Skip to content
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

Shrink the size of a Ref in memory for some B: ByteSlice #368

Open
joshlf opened this issue Sep 11, 2023 · 1 comment
Open

Shrink the size of a Ref in memory for some B: ByteSlice #368

joshlf opened this issue Sep 11, 2023 · 1 comment
Labels
compatibility-nonbreaking Changes that are (likely to be) non-breaking experience-hard This issue is hard, and requires a lot of experience help wanted Extra attention is needed

Comments

@joshlf
Copy link
Member

joshlf commented Sep 11, 2023

Currently Ref is defined as:

pub struct Ref<B, T: ?Sized>(B, PhantomData<T>);

This has the consequence that, even if T: Sized, when in principle Ref could be a single-word thin pointer, Ref is still as large as B. Usually this is two words (e.g. for &[u8]), but sometimes it's more (e.g. for core::cell::Ref, which has a reference count).

For some types, this is unavoidable. E.g., no matter what we do, we need to store the entire core::cell::Ref - if we didn't, we'd be leaking memory. But for some types - such as &[u8] - we can reconstruct the original B: ByteSlice just from a pointer to T.

Thus, I propose the following addition to ByteSlice:

trait ByteSlice {
    // Name to be bikeshedded later. We may not want to tie the name to the `Ref`
    // type since we plan on maybe making `ByteSlice` unsealed at some point.
    //
    // For `&[u8]`/`&mut [u8]`, this is just `NonNullFoo<T>`. For `core::cell::Ref`/
    // `core::cell::RefMut`, this is just `Self`.
    type Foo<T: ?Sized>: AsRef<[u8]> + AsMut<[u8]>;

    // Either discards `foo`, constructing `Foo` from `self` (e.g. for `core::cell::Ref`),
    // or discards `self`, constructing `Foo` from `foo` (e.g. for `&[u8]`).
    //
    // SAFETY: Caller promises that `ptr` references a sequence of bytes which are
    // all initialized. That ensures that we can implement `AsRef<[u8]>`/`AsMut<[u8]>`
    // without a `T: AsBytes` bound.
    unsafe fn into_foo<T: ?Sized>(self, foo: NonNullFoo<T>) -> Self::Foo;
}

struct NonNullFoo<T> {
    // Invariant: Always references a fully-initialized sequences of bytes. This is fine
    // because this is always constructed from a `B: ByteSlice` during `Ref` construction.
    inner: NonNull<T>,
}

// NOTE: Caller needs to ensure the `NonNullFoo` doesn't live for too long since
// these methods are not `usnafe`. Might need to be a safety requirement on
// `ByteSlice::into_foo`.
impl<T> AsRef<[u8]> for NonNullFoo<T> { ... }
impl<T> AsMut<[u8]> for NonNullFoo<T> { ... }

Then, we update Ref:

struct Ref<B, T: ?Sized>(<B as ByteSlice>::Foo<T>, PhantomData<B>);

cc @kupiakos , I know you were interested in having this, and I remember you mentioned taking a stab at some ideas for how to implement it

@joshlf joshlf added help wanted Extra attention is needed experience-hard This issue is hard, and requires a lot of experience compatibility-nonbreaking Changes that are (likely to be) non-breaking labels Sep 22, 2023
@joshlf
Copy link
Member Author

joshlf commented Dec 16, 2023

Idea for future update: Once we support unsized MaybeValid<T>, we could store cell::Ref<MaybeValid<T>> inside of Ref instead of storing cell::Ref<[u8]>. This would eliminate the need to re-compute the &T at runtime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compatibility-nonbreaking Changes that are (likely to be) non-breaking experience-hard This issue is hard, and requires a lot of experience help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant