-
Notifications
You must be signed in to change notification settings - Fork 183
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
Make DataPayload
constructible from &'static M::Yokeable
#3467
Conversation
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
approach seems close to what we discussed.
Am hoping we can potentially reduce this down to Yoke<&'static M, C>
with some tricks. I think it's possible by making the cart type more complex, and having with_mut()
extract the cart object
pub struct DataPayload<M: DataMarker>(pub(crate) DataPayloadInner<M>); | ||
|
||
pub(crate) enum DataPayloadInner<M: DataMarker> { | ||
Yoke(Yoke<M::Yokeable, Option<Cart>>), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: worried about stack size increase, and worried about runtime cost
Admittedly I do not know if there is a way around this, I just want to register the concern. It may be insurmountable and we can decide to do this anyway
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
though this does become easy to cfg
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Runtime cost: solved by using as_borrowed
, and the fact that we don't need to ZeroFrom
everything.
Stack size: it seems to be, at worst, one extra word per payload. Maybe if we make Yoke have better niches, we could get this to be stack-neutral.
provider/core/src/response.rs
Outdated
DataPayload(match self.0 { | ||
DataPayloadInner::Yoke(yoke) => DataPayloadInner::Yoke(yoke.map_project(f)), | ||
DataPayloadInner::Ref(r) => { | ||
DataPayloadInner::Yoke(Yoke::new_owned(r.clone()).map_project(f)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: this does have a cost. I'm not sure how we can avoid this cost. However, there are a bunch of cases in the code where we don't need owned mapping: ought we introduce map_project_ref()
that does not require cloning carts, but does operate on references?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess there are a few places in datetime
where we map_project in runtime code, so I agree it would be nice if we didn't need to downgrade to ZeroFrom, but I'm not sure if that's avoidable in the general case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not avoidable in the general case, it is avoidable in DateTime.
Unfortunately it's not avoidable in the map property FFI code without significant changes :/ But we might be able to figure out a different way to type-erase there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently we pay this cost on every DataPayload
construction. With this PR, we only pay the cost when we map_project
, so this is a strict improvement.
map_project_cloned()
already operates on references.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but it also clones the cart, which isn't great.
Something that operates on self
, uses &Y
and doesn't clone the cart is the missing niche here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only looked at the files in provider/core/src because I think the rest is from another PR
pub struct DataPayload<M: DataMarker>(pub(crate) DataPayloadInner<M>); | ||
|
||
pub(crate) enum DataPayloadInner<M: DataMarker> { | ||
Yoke(Yoke<M::Yokeable, Option<Cart>>), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Runtime cost: solved by using as_borrowed
, and the fact that we don't need to ZeroFrom
everything.
Stack size: it seems to be, at worst, one extra word per payload. Maybe if we make Yoke have better niches, we could get this to be stack-neutral.
DataPayloadInner::Ref(r) => { | ||
let output: <M2::Yokeable as Yokeable<'static>>::Output = | ||
f(Yokeable::transform(*r), PhantomData); | ||
// Safety: <M2::Yokeable as Yokeable<'static>>::Output is the same type as M2::Yokeable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: just downgrade the thing to a Yoke via ZeroFrom instead of doing these unsafe gymnastics
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? f
operates on a reference so we can use that to avoid doing the expensive ZeroFrom which we're trying to avoid with this PR. It's not gymnastics if it's correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, well there's still necessarily a Clone
or ZeroFrom
happening inside of the f
function, but the advantage is that in map_project_cloned
we only need to do that on a subset of fields instead of the whole thing.
I think the safety comment is not quite right; the invariant is "The returned value must be destroyed before the data from was borrowing from is." The fact that you're setting 'a
to 'static
on line 390 is important to the safety of the borrowed data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think "The returned value must be destroyed before the data from was borrowing from is." is irrelevant. The point here is that the types are the same but the compiler doesn't know, I could also use a transmute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well I can't use a transmute because the compiler doesn't know they're the same size
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would ideally like to avoid having any unsafety here.
I think all we need here is fn upgrade_output<Y: Yokeable<'static>>(output: Y::Output) -> Y
, which internally calls Yokeable::make()
. Does that make sense? Yokeable::make()
is only scarily unsafe when the lifetimes involved aren't 'static
.
This PR starts at the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM for the changes not in the diffbase
This comment was marked as spam.
This comment was marked as spam.
Self(DataPayloadInner::Yoke(Yoke::new_owned(data))) | ||
} | ||
|
||
#[doc(hidden)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be public?
Discussed with @robertbastian: this PR is not a strict improvement. There are pros and cons:
|
I think the way to do this is probably |
I think the way to do this is probably |
It would have to be |
Hmm. Yeah maybe, depends on what we want to achieve. |
If we can get The call sites of I have long thought that we need to rethink the data model of datetime formatters, and I made a comment recently that we should just resolve patterns into |
Yeah though we also But yes, that's what I was saying when I was talking about a |
Another thing I was thinking was that users who really want to map_project to a new type should use a new type like DataPayloadAlwaysYoke, or just Yoke directly. It doesn't solve the problem for datetime since those structs have really big stack size, but it works great for properties where the stack is not very big. |
I've added the databake AnyProvider simplification that this unlocks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm satisfied with the short-term and medium-term work that this change unlocks.
I removed the datagen changes to keep this PR focused. |
DataPayload(DataPayloadInner::Yoke( | ||
match self.0 { | ||
DataPayloadInner::Yoke(yoke) => yoke, | ||
DataPayloadInner::StaticRef(r) => Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, this isn't expensive anymore, neat
DataPayloadInner::Ref(r) => { | ||
let output: <M2::Yokeable as Yokeable<'static>>::Output = | ||
f(Yokeable::transform(*r), PhantomData); | ||
// Safety: <M2::Yokeable as Yokeable<'static>>::Output is the same type as M2::Yokeable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would ideally like to avoid having any unsafety here.
I think all we need here is fn upgrade_output<Y: Yokeable<'static>>(output: Y::Output) -> Y
, which internally calls Yokeable::make()
. Does that make sense? Yokeable::make()
is only scarily unsafe when the lifetimes involved aren't 'static
.
Part of #3020
This adds
M::Yokeable: ZeroFrom<'static, M::Yokeable>
bounds toDataPayload::map_project
andDataPayload::try_map_project
. This is a breaking change, howeverZeroFrom
is implemented for all data structs (through#[data_struct]
or convention).