[Haskell-cafe] question re: FFI and ensuring unwrapped values are present on stack
Charles Strahan
charles.c.strahan at gmail.com
Tue Mar 4 04:05:22 UTC 2014
Hello Adam,
Thanks for the suggestions! I considered ForeignPtrs+finalizers, but I'd
like to avoid that due to weak guarantees you cite.
I do like the idea about using existentially quantified types to prevent
leakage, and might give that more consideration (I can still see some use
in allowing users to keep references to globals and such, granted they
could just pull them out of the Ruby object graph again whenever they need
a reference...).
However, I think I have arrived at a design I'm fairly happy with; I've
written an 'RBIO' monad that's just a newtype wrapper around (StateT RValue
IO a). Now, when the Ruby interpreter calls into my Haskell method
definitions, it passes along an array which is then used as the state in my
RBIO monad. Now, any other monadic interactions with the Ruby interpreter
append the resulting object references to the array, and since the array is
placed on Ruby's stack, all of those referenced objects get marked.
The end user API ends up looking something along the lines of:
newtype RValue = RValue (Ptr RValue) deriving (Eq, Ord, Show, Typeable,
Data, Storable)
newtype RBIO a = RBIO { runRBIO :: StateT RValue IO a } deriving (Monad,
MonadIO)
evalRBIO :: RValue -- array (used to prevent GC)
-> RBIO a -- RBIO to eval
-> IO a
defMethod :: RValue -- class
-> String -- method name
-> (RValue -> [RValue] -> Maybe RValue -> RBIO RValue) -- (self,
args, block, result)
-> RBIO ()
rbCall :: RValue -- self
-> String -- method name
-> [RValue] -- args
-> Maybe RValue -- optional Ruby block/lambda
-> RBIO RValue
I'll follow up when I make the source available - so far, I think it's
working out pretty well!
-Charles
On Mon, Mar 3, 2014 at 6:17 PM, adam vogt <vogt.adam at gmail.com> wrote:
> Hello Charles,
>
> I don't know what can be done for your option 1, but I have two
> suggestions for your option 2:
>
> Did you consider making RValue a ForeignPtr and adding a FunPtr to
> rb_gc_unregister as the finalizer? That will end up being a bit less
> restrictive than your "withObject", in exchange for a weaker guarantee
> as to when the finalizer actually runs.
>
> Also you might use the same trick that Control.Monad.ST does to
> prevent references from leaking out. In other words, change your
> definitions to look more like:
>
> newtype RValue s = RValue (ForeignPtr ())
>
> withObject :: (forall s. RValue s -> IO r)
> -> IO r
>
> With that trick, (withObject return) becomes a type error.
>
> Regards,
> Adam
>
>
> On Mon, Mar 3, 2014 at 12:41 AM, Charles Strahan
> <charles.c.strahan at gmail.com> wrote:
> > Hello all,
> >
> > This is probably going to be a pretty niche question, but I'm hoping
> someone
> > here can lend a hand.
> >
> > I'm working on a binding for the YARV Ruby VM, and I'm struggling to
> come up
> > a with a good interop story with respect to its GC implementation. I have
> > two options to prevent premature GC of Ruby object pointers:
> >
> > 1) Guarantee that the pointers reside on the stack or in registers,
> > 2) or copy the pointer itself to another static area of memory and
> register
> > that address with the GC.
> >
> > So, the big question is: is there a way to make #1 a reality, while
> > operating in the IO monad?
> >
> > I can picture something like the following:
> >
> > newtype RValue = RValue (Ptr RValue) deriving (Storable)
> >
> > withObject :: IO RValue -- e.g. result of some ccall, or some
> > further transformation thereof
> > -> (RValue -> IO a) -- the RValue is now on the stack (or in
> a
> > register), available for use without worry of premature GC
> > -> IO a -- the result of computation
> >
> > With Haskell being a non-strict language, I understand that there are a
> lot
> > of instances where a value could end up on the heap, with a thunk being
> > passed via stack/registers. For my needs, I'd need some way to guarantee
> > that the RValues being passed to and from withObject are at all times
> kept
> > unwrapped and off the heap, and likewise within the second argument.
> >
> > Given my rather limited knowledge of Haskell (I've been programming in
> > Haskell for about a month now), I think the best I can come up with is
> the
> > second option suggested above:
> >
> > withObject :: (Ptr RValue -> IO ()) -- e.g. void some_fun(VALUE* out)
> {...}
> > -> (RValue -> IO a) -- e.g. do something with `out`
> derefed
> > -> IO a
> > withObject f g =
> > alloca $ \ptr -> do
> > rb_gc_register_address ptr -- prevent GC
> > f ptr -- call wrapped native function
> > val <- peek ptr -- deref the VALUE*
> > res <- g val -- pass to function
> > rb_gc_unregister_address ptr -- clean up
> > res
> >
> > So, while the above would work, it's really, really ugly (IMO). That, and
> > I'd have to wrap all the native functions so that instead of returning
> > VALUE, they'd write to some out ptr and return void. And there's the
> > otherwise unnecessary alloca.
> >
> > I hope that was clear enough - any advice would be super duper
> appreciated!
> >
> > I'm assuming that there isn't a way I can get away with using the typical
> > monadic bind syntax while keeping the RValues off the heap; if I'm wrong,
> > and I can skip the whole withObject/HOF approach, I'd love to know it!
> I'm
> > not (yet) familiar enough with the compiler / language spec to know about
> > such guarantees, should they exist...
> >
> > - Charles
> >
> > _______________________________________________
> > Haskell-Cafe mailing list
> > Haskell-Cafe at haskell.org
> > http://www.haskell.org/mailman/listinfo/haskell-cafe
> >
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-cafe/attachments/20140303/f950f03d/attachment.html>
More information about the Haskell-Cafe
mailing list