darcs patch: try to abstract IO a bit
Hi John,
This is an attempt to move towards the point where I could try
implementing concurrent IO. I am not sure whether this approach will
work at all, so I'll appreciate your feedback. I'll probably send a
separate email with more details on what I'm thinking.
This patch is essentially a cleanup, which moves the newtype of IO
into Jhc.IO and avoids exporting its constructor. The hope is that
I can later change the type of IO, perhaps to something like
newtype OldIO a = IO (World__ -> (# World__, a #))
newtype IO a =
IO { runIO :: (a -> ThreadState -> OldIO ()) -- ^ a continuation
-> ThreadState -- ^ the current state
-> OldIO () } -- ^ the combined computation
although I'd also want to figure out how to work exceptions into the
picture.
Anyhow, I think this patch is reasonable on its own. My greatest
uncertainty at the moment due to my lack of understanding of how you
deal with FFI calls. i.e. how is the code for a delcaration like
foreign import ccall "stdio.h jhc_utf8_putc" c_putc :: Ptr CFile -> Int -> IO ()
generated? I imagine that some work will have to happen in the core
compiler in order for this import to generate a call to Jhc.IO.io
(which is what I'd like).
That's all for now... I'm off to have brunch. :)
David
Sat Sep 5 10:45:15 EDT 2009 David Roundy
Sorry, this patch is missing a crucial element. Please wait for an
amended patch...
David
On Sat, Sep 5, 2009 at 10:54 AM, David Roundy
Hi John,
This is an attempt to move towards the point where I could try implementing concurrent IO. I am not sure whether this approach will work at all, so I'll appreciate your feedback. I'll probably send a separate email with more details on what I'm thinking.
This patch is essentially a cleanup, which moves the newtype of IO into Jhc.IO and avoids exporting its constructor. The hope is that I can later change the type of IO, perhaps to something like
newtype OldIO a = IO (World__ -> (# World__, a #))
newtype IO a = IO { runIO :: (a -> ThreadState -> OldIO ()) -- ^ a continuation -> ThreadState -- ^ the current state -> OldIO () } -- ^ the combined computation
although I'd also want to figure out how to work exceptions into the picture.
Anyhow, I think this patch is reasonable on its own. My greatest uncertainty at the moment due to my lack of understanding of how you deal with FFI calls. i.e. how is the code for a delcaration like
foreign import ccall "stdio.h jhc_utf8_putc" c_putc :: Ptr CFile -> Int -> IO ()
generated? I imagine that some work will have to happen in the core compiler in order for this import to generate a call to Jhc.IO.io (which is what I'd like).
That's all for now... I'm off to have brunch. :)
David
Sat Sep 5 10:45:15 EDT 2009 David Roundy
* try to abstract IO a bit
-- David Roundy
An issue is that the newtype for IO needs to be in Jhc.Prim, Jhc.Prim is included as a dependency for every module because it has things that the compiler needs to exist no matter what you are building. This doesn't mean that Jhc.IO has to export it non-abstractly, and allowing it to be fully abstract is useful, but we need to move the actual newtype back into Jhc.Prim. Just because an IO in Jhc.Prim exists, it doesn't mean you can't provide an alternate IO via a package of course. You might be interested in Jhc.JumpPoint, which was the original implementation of IO exceptions. now, we actually have Grin primitives for continuations, though they arn't currently used, they may be useful for what you are doing. See 'MkCont' in Grin.Grin. John -- John Meacham - ⑆repetae.net⑆john⑈ - http://notanumber.net/
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.
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.
David
On Sat, Sep 5, 2009 at 8:08 PM, John Meacham
An issue is that the newtype for IO needs to be in Jhc.Prim, Jhc.Prim is included as a dependency for every module because it has things that the compiler needs to exist no matter what you are building. This doesn't mean that Jhc.IO has to export it non-abstractly, and allowing it to be fully abstract is useful, but we need to move the actual newtype back into Jhc.Prim.
Just because an IO in Jhc.Prim exists, it doesn't mean you can't provide an alternate IO via a package of course.
You might be interested in Jhc.JumpPoint, which was the original implementation of IO exceptions. now, we actually have Grin primitives for continuations, though they arn't currently used, they may be useful for what you are doing. See 'MkCont' in Grin.Grin.
John
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.
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. 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. John -- John Meacham - ⑆repetae.net⑆john⑈ - http://notanumber.net/
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
participants (3)
-
David Roundy -
David Roundy -
John Meacham