matsadler / magnus

Ruby bindings for Rust. Write Ruby extension gems in Rust, or call Ruby from Rust.

Home Page:https://docs.rs/magnus/latest/magnus/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Discussion: Pin Ruby values to the stack

ekroon opened this issue · comments

I was wondering if you considered stack pinning for arguments as an alternative solution for ensuring Ruby values are not removed from the stack?

I am trying to prototype it a bit and I am not sure this is possible without breaking existing code, but it seems like something that would be helpful?

Maybe you tried / considered and found good reasons to not do this?

  • #46 is relevant for this.

I've not thought about this in a while, but I think this is the chain of reasoning I had for not trying this out:

There the Pin type, but only tells you that some data has a stable address, it doesn't tell you if that address is on the stack. There's nothing in the type system that can tell you a value is on the stack. You could do something similar to say NonZeroI64, where there's a runtime check on creating the value, and then from there on you can rely on the type system. I don't know if it's possible to runtime assert a value is on the stack, so that leaves the programmer just promising it is, which is the same situation we're in now, but the code is uglier (Pin<Something> can be self, but if we have to use our own type then we can't use methods) and more complicated. It also might lead people down the path of thinking if they pin the value to the stack just before calling a method on it then they are safe, when in fact the value needs to have been pinned from the moment it was created.

It's possible I missed something, or there's another approach that'd work.


I do have in the back of my mind a way to, behind the scenes, keep values on the stack as part of the automatic type conversion when binding Rust functions to Ruby methods. So that you could bind functions taking &str, and borrow the Ruby string. But I've not gotten as far as prototyping it so I'm not sure if it's even possible.

You could do something similar to say NonZeroI64, where there's a runtime check on creating the value, and then from there on you can rely on the type system. I don't know if it's possible to runtime assert a value is on the stack ...

There are ways to check if a value is on the stack, but that seems more like something that can be provided as a verification step, as I can imagine this would create a decent amount of overhead.

The best I've come up with is to have something like a GcGuardedValue<'a> that calls rb_gc_guard! in the Drop impl.

My memory is a little fuzzy, but when I did this a long time ago this prevented LLVM from optimizing values off of the stack. However, requires that:

  1. The inner Value type is !Copy
  2. Imposes lifetimes on the Value

This kills the ergonomics of the said library, and IMO is not worth the trade-off. Maybe there's another way with Pin but I imagine the above 2 constraints would be necessary. Would be interested to hear if someone comes up with something else.

I think we can keep a reference on the Ruby side to avoid this.

Here is an example:


class RubyStore
  def insert(key, val)
    @export_table << key
    @export_table << val
    insert_inner(key, val)
  end
end

The insert_inner inserts the key and val to a Rust struct which is wrapped to the RubyStore object. To avoid GC, the insert inerts the key and val in a local variable.

But this is not enough, as the objects can be moved during GC, so we need to pin those objects. And Fiddle::Pinned can achieve.

But I'm not sure if there is a better solution.