unsafePerformIO around FFI calls

Hal Daume III hdaume@ISI.EDU
Tue, 23 Jul 2002 09:09:06 -0700 (PDT)


I think I've got a pretty good idea of the circumstances that make
unsafePerformIO not unsafe.  For instance, I would imagine it would be
safe to wrap unsafePerformIO around the following C function:

  int myfunc(int i) {
    int[256]arr;
    for (int j=0; j<256; j++)
      arr[j] = i+j;
    int foo = 0;
    for (j=0; j<256; j++)
      foo += arr[j] / i;
    return foo;
  }

Because even though it does impure things (allocate/deallocate memory, for
instance), there's no way from the Haskell side of things to differentiate
this function from one that is pure and just loops and does the calculate
directly.

If this is true, then is it equivalently safe to wrap the following
Haskell action in unsafePerformIO:

  myFunc i =
    do arr <- newArray (0,255) 0
       mapM_ (\j -> writeArray arr j (i+j)) [0..255]
       foo <- newIORef 0
       mapM_ (\j -> readArray arr j >>= modifyIORef foo (+j)) [0..255]
       readIORef foo >>= return

? (don't call me on syntax errors -- i haven't checked this at all, but
you should get the idea)

 - hal

--
Hal Daume III

 "Computer science is no more about computers    | hdaume@isi.edu
  than astronomy is about telescopes." -Dijkstra | www.isi.edu/~hdaume

On Wed, 10 Jul 2002, C.Reinke wrote:

> 
> > > I'm curious exactly what is "safe" and what is "unsafe" to wrap
> > > unsafePerformIO around when it comes to FFI calls. 
> > 
> > Here's a simple test:
> > 
> >  Could you imagine an alternative implementation of the same API in
> >  pure Haskell?  (Don't consider efficiency or effort required to write
> >  the implementation, just whether it can be done.)
> > 
> >  If so, then it is ok to use unsafePerformIO and the ffi to implement
> >  the API instead.
> 
> That looks simple, but is not unproblematic: 
> 
> - just because I can *imagine* a pure implementation of *the API I want*, 
>   that doesn't mean that the foreign implementation in question *is* pure
>   (*the exact API it implements* might have some extra features or
>    assumptions, such as single-threaded use).
> 
> - so the test implicitly depends on a very strict interpretation of 
>   "the same API", including all observable side-effects, and that 
>   isn't quite as simple as you make it.
> 
> > If it fails that test, it is incredibly unlikely that it is ok and a
> > proof that it is ok is likely to be pretty complex - maybe worth a
> > PLDI paper or some such.
> 
> - just because I can't implement something in pure Haskell doesn't 
>   mean that it has to be impure, or unsafe (it might fall into the 
>   gaps of static typing, for instance).
> 
> So it seems to boil down to the usual argument about
> unsafePerformIO, no matter whether the IO action is foreign or not:
> by casting from "IO a" to "a", you *assert* that the IO part of your
> computation is not observable, and it is *your obligation to show*
> that this assertion is correct. You don't have to do a formal proof,
> but while attempting an informal one, you might find
> *side-conditions* (on the usage of your function) on which your
> assertion depends. The most valuable part of the proof attempts is
> to *identify and document*, *for your particular case*, those
> side-conditions. In other words, to become aware of "exactly what is
> `safe' and what is `unsafe' to wrap unsafePerformIO around"!-)
> 
>   Can your implementation defend itself against all attempts to 
>   discover that its "a" is really an "IO a"?
> 
>   If so, you're safe (or feel so, at least;-). If not, document the
>   gaps in your defense (they might not arise in typical or intended
>   use). If there are too many or too serious gaps, you're definitely 
>   "unsafe".
> 
> Beware of optimizing compilers or other meaning-preserving
> program-transformation tools. The more you want your tools to make
> use of the semantics of your programs, the less you want to cheat on
> those semantics (is your foreign implementation still pure in a
> multi-threaded setting?). It's not called unsafePerformIO for
> nothing - using it means "trust me, I know what I'm doing". Do you?
> 
> As for FFI-specifics, the problem seems to *find out* what the
> *precise API* of your foreign function is (including side-effects),
> and what *extra impurities* the FFI wrapping might impose. Given
> that the foreign function is imported as "IO a", the FFI wrapping
> would be permitted to use "IO", e.g., during marshalling. Does it?
> 
> Cheers,
> Claus
> 
> _______________________________________________
> Haskell mailing list
> Haskell@haskell.org
> http://www.haskell.org/mailman/listinfo/haskell
>