-
Notifications
You must be signed in to change notification settings - Fork 47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Plan for HIR lifetimes support #406
Comments
Going to flesh out the different possible approaches for #410 (comment) . Focusing on Dart here but JS will need to take basically the same route. To start out with, here are the things I am trying to care about:
Here's a rundown of the approach in #410 for this, which does not handle slices:
A core part of this design is that structs are considered fully transparent: they do not themselves hold on to lifetime edges; but they know how to pass them down to their fields, or extract the right fields for a given lifetime. Changing this would mean teaching the structs to track mutation, which risks leaking things since you can't know for sure which lifetime edges to drop after mutation. This doesn't handle slices since slices do not borrow from the slice parameter, they borrow from temporary intermediary arenas. Approaches for handling slicesFor the purpose of this comment, Let's deal with the following code: struct Bag<'x, 'y, 'z> {
slice: &'x [u8],
opaque: &'y Opaque<'z>
}
// on opaque Foo
fn foo<'a, 'b, 'c>(bag: Bag<'a, 'b, 'c>, other_slice: &'c [u8] ) -> OtherOpaque<'a, 'c>;
struct Opaque<'x>;
struct OtherOpaque<'x, 'y>; Currently this will generate something like this OtherOpaque foo(Bag bag, ByteBuffer bytes) {
final temp = ffi2.Arena();
List edges_for_lifetime_a = [...bag.fields_for_lifetime_x()]; // this produces `[bag.slice]`
List edges_for_lifetime_c = [bytes, ...bag.fields_for_lifetime_z()]; // This ends up being [bytes, bag.opaque]
final result = _Foo_foo(fields._pointer(temp), other_slice.pointer(temp));
return OtherOpaque.(result, /* isOwned */ true, edges_for_lifetime_a, edges_for_lifetime_c);
}
(ignore my Rusty naming style right now: we should fix that for camelCase but it's not urgent) Approach A:
|
Somewhat depends on conclusion of #405
@QnnOkabayashi wrote lifetimes support for the AST JS backend and also designed lifetimes stuff for HIR, including the elision module which basically figures out lifetime elision in methods (quite challenging!!).
Unfortunately, we never wrote an HIR backend back then so we didn't really get to battle test the APIs.
We do have actual backends using HIR now (C2, CPP2, Dart, soon hopefully JS: @robertbastian and I are working on it). We now know what they look like.
What I'm hoping is that the backends have to do as little work as possible to get the information they need. This may involve precomputing more info about lifetimes than we do already.
The status quo is that every method contains a
LifetimeEnv
(which tracks bounds, etc, and has already dealt with elision). You can also get aBorrowingFieldsVisitor
from it, which eventually tells you which input lifetime corresponds to which output field lifetime.Every type name (as found in a method or struct field, not a def) has a TypeLifetimes that lists which of the lifetimes of the surrounding method/struct are found in its path, and in what order. These can be converted to MethodLifetimes, that are in the context of a surrounding method.
This is a start, but I think we need more. The end state I would like to see is (partially started in #404) :
LifetimeEnv<Type>
. Since structs don't have fancy elision going on we can construct these in a much simpler way.Lifetimes
for every struct field: basically something likeLifetimeEnv<Type>.new_lifetimes_for(&field)
, producing a list of relevant indices. This list is transitively evaluated.new_lifetimes_for()
to make sure the struct ctor correctly flows lifetimes into its fields.BorrowingFieldsVisitor
to figure out which input param/field corresponds to which output lifetime. Once it has constructed the edge arrays, it flows them correctly.enter_field()
exit_field()
when converting all method params and their struct fields to Rust typesedges
arrays.edges_a
,edges_b
, etc arrays, and just push to the array when you get here.enter_output_field()
etc, and it will return one or more MethodLifetimes for each field that correspond to each edge for that field.The end result of this would be that adding lifetime support to a backend that eagerly constructs structs (see #405) is just a matter of sprinkling in a couple method calls; nothing fancy. We can have BorrowingFieldsVisitor be an internal type that contains the actual field visiting logic.
The text was updated successfully, but these errors were encountered: