On Mon, Sep 7, 2009 at 6:40 PM, John Meacham
On Mon, Sep 07, 2009 at 07:26:26AM -0400, David Roundy wrote:
Hmm. After experimenting a bit more, I think I'm going to give up on this particular project. I don't see a way to introduce a new IO without duplicating most/all of base and jhc. And even if I were to do that, it doesn't look like the result would work with FFI without requiring users to manually wrap FFI calls into IO from Jhc.Prim.IO.
Yeah, it would be tricky. The best way I could think of would be to have multiple versions of just the jhc library, and link base against different versions of it. it doesn't help with the FFI issues though and would still have issues. I am always looking for ways to turn "compiler-magic" into things that can be expressed to jhc by the user, but IO is fairly ingrained at the moment.
Yeah, I think that'd be the best way to do things, and is roughly what I started working on recently (except for the first draft I figured to rebuild both jhc and base libraries and then see what I had to change). As far as the FFI issue, I can't help but wonder if you could do something like the following. If we define Jhc.IO.wrapIO :: UIO a -> IO a then we could desugar foreign import ... foo :: Int -> Bool -> IO Char to foreign import ... foo__UIO__ :: Int -> Bool -> UIO Char foo :: Int -> Bool -> UIO Char foo a b = wrapIO (foo__UIO__ a b) Since this is something I could easily imagine doing as a preprocessing stage, it seems like it ought to be easy enough to do in the compiler itself. Then we ought to be able to arbitrarily redefine IO, as long as we wrote a function to convert from a raw UIO a to IO a. Does this sound reasonable to you? Would it be easy for you? I took a look at the relevant code, and concluded that it wouldn't be easy for me.
I find the idea of writing the Haskell runtime in plain old Haskell, simply by wrapping a less featureful IO very appealing, albeit likely to be less efficient. But alas, it doesn't look pleasant trying to work this as an option into Jhc. btw, I've gotten my Haskell IO monad to do exceptions as well as concurrency... although of course it can't handle errors in pure code without some sort of help... but then, that's inevitable. All implemented without even unsafePerformIO or unsafeInterleaveIO... which are themselves still unimplemented, and probably won't be able to spawn threads until such time as I move the ThreadState into an IORef or something of the sort. But then, an unsafePerformIO that spawns a thread that outlasts its result is a pretty unholy beast.
Yes, those ideas are definitely appealing. I originally implemented exceptions directly in the IO monad, but found them to greatly clutter up the generated code for paths that were rarely used. I ended up thinking a middle ground would be in order, I didn't want to hardcode too much in a static C RTS, but implementing it directly in haskell seemed problematic, so I decided to include some appropriate Grin primitives and use those. Grin is a purely functional first order language, so it is still fairly easy to optimize, but it is close enough to what is actually run on the hardware that reasoning about things like exceptions and run-time cost is feasable.
Yeah, I don't think that the pure Haskell approach to exceptions (or concurrency) is necessarily going to win in the long run... it almost certainly won't. But having such an implementation still seems like a good idea. For one thing, it could be portable across compilers. And who knows? It may be that with adequate optimization, you can make such an implementation behave just as well as a more low-level implementation. And, of course, a second implementation may be helpful in ferreting out bugs in the first.
As for concurrency, I am leaning more towards a single pthread per haskell thread, however, doing things like blackholing would be exceedingly tricky in jhc, which raises the question of whether it actually is needed. Work may be repeated in theory, but I don't know if that will be an issue in practice. unsafePerformIO is more of an issue. Perhaps explicit locking can be used for those odd cases... In any case, I'd want it to be something that is relatively independent of the rest of the compiler, and can be turned on or off on a case-by-case basis. like how the garbage collector is fairly decoupled.
That sounds reasonable to me. I'm pretty well convinced that my pure haskell approach won't perform very well. But then, that may not matter for most workloads, in particular for many things that I do. And as with a pure-Haskell exception implementation, it seems valuable to have multiple implementations. e.g. it might be nice to experiment with better semantics than those provided by ghc, such as having the program not exit until all threads have exited. David