Skip to content

Add WriteOnly type to replace giving &mut [u8] access to mapped buffers.#9042

Open
kpreid wants to merge 2 commits intogfx-rs:trunkfrom
kpreid:writeonly
Open

Add WriteOnly type to replace giving &mut [u8] access to mapped buffers.#9042
kpreid wants to merge 2 commits intogfx-rs:trunkfrom
kpreid:writeonly

Conversation

@kpreid
Copy link
Collaborator

@kpreid kpreid commented Feb 12, 2026

Connections
Fixes #8897.

Description
When accessing a buffer for writing, do not expose (or internally create) any &'a mut [u8] references to that memory, so as to avoid exposing write-combining memory that might visibly misbehave in atomic operations. Instead, expose a new pointer type WriteOnly<'a, [u8]> which offers only write operations. This can be sliced and reborrowed, allowing it to be used in most ways which &mut [u8] would be, hopefully with near-zero overhead.

This pointer type might also allow users to opt in to uninitialized memory for performance, while confining the consequences to GPU UB rather than Rust UB. However, that is a future possibility and is not implemented in this PR other than as a consideration in defining the API of the WriteOnly type.

There are already published libraries that implement a write-only pointer type, such as wref, outref; however, they are lacking in operations like chunking and IntoIterator which I believe are necessary to provide reasonable ergonomics for our users’ use cases. An alternative to this PR’s unsafe code would be to contribute changes to one of those libraries to meet our needs.

Testing
This PR has near-complete test coverage for the WriteOnly type through a mix of doctests and #[test]s. These tests are run under Miri in a new CI job.

Squash or Rebase?
Rebase

Checklist

  • Run cargo fmt.
  • Run taplo format.
  • Run cargo clippy --tests. If applicable, add:
    • --target wasm32-unknown-unknown
  • Run cargo xtask test to run tests.
  • If this contains user-facing changes, add a CHANGELOG.md entry.

@kpreid kpreid marked this pull request as draft February 12, 2026 00:32
@inner-daemons
Copy link
Collaborator

Ok I'm going off the generated docs so these won't be comments on the code but I have some questions. Overall trust your opinion more than mine.

  • Would this be worth its own crate or something?
  • WriteOnly::write consumes self? There doesn't seem to be a good enough reason for this. Easy to imagine a situation where someone wants to write the entire thing and then overwrite parts of it
  • Missing clone_from_slice? - not relevant for [u8]
  • Looks like it is missing the Index<Range<..>> stuff for more ergonomic slicing

@kpreid
Copy link
Collaborator Author

kpreid commented Feb 12, 2026

Would this be worth its own crate or something?

As I mentioned in the PR description, there are a couple already. I think that if we put effort into separate versioning it would make sense to see if we can contribute to one of those efforts instead of publishing yet another variation — and if not, we get to tweak it to be maximally ergonomic for writing into wgpu byte buffers without worrying about long-term API stability.

WriteOnly::write consumes self? There doesn't seem to be a good enough reason for this. Easy to imagine a situation where someone wants to write the entire thing and then overwrite parts of it

I’ve never seen such a situation, and if there was one, I’d expect it to be acting on a slice rather than a Sized value — write() only applies to Sized values. If this does come up, you just write .slice(..) to reborrow.

Missing clone_from_slice? - not relevant for [u8]

Not just [u8] — currently there are no methods for writing anything that is not Copy, so that it’s impossible to accidentally use the API to forget a value by overwriting it.

Looks like it is missing the Index<Range<..>> stuff for more ergonomic slicing

Index and IndexMut cannot be implemented because they return & and &mut respectively, so they imply the permission to read.

Copy link
Contributor

@madsmtm madsmtm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re in wgpu vs. outref/wref, an option in between could be to make a new crate (possibly in a separate gfx-rs repo?) - that'd make it possible to tune the API exactly for wgpu's needs, while also making it easier to test with Miri.

Copy link
Member

@cwfitzgerald cwfitzgerald left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is insanely high quality stuff. I definitely think this is the right approach, have one nit. For miri, we could also enable just the tests from naga/wgpu/wgpu-core/wgpu-hal initially, as those don't require the GPU at all and don't take much time to run.

@inner-daemons inner-daemons self-requested a review February 18, 2026 18:12
@kpreid kpreid force-pushed the writeonly branch 2 times, most recently from 2f196ef to 448f31e Compare February 21, 2026 07:19
@kpreid kpreid marked this pull request as ready for review February 21, 2026 07:21
@kpreid
Copy link
Collaborator Author

kpreid commented Feb 21, 2026

I stayed up entirely too late and I may regret this, but there are tests now, and I think this is basically ready for review, though definitely not ready for merging.

@kpreid kpreid force-pushed the writeonly branch 3 times, most recently from 8334940 to 0c6e5dc Compare February 21, 2026 07:36
///
/// [mapped]: Buffer#mapping-buffers
#[track_caller]
pub fn get_mapped_range(&self) -> BufferView {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add (if one doesn’t already exist) a test checking that one cannot get a read-access BufferView to a write mapping. Or if that is required to be possible by the spec, then things still might be fine, but some of the documentation I added needs to be changed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per exploratory testing, and discussion in maintainers meeting:

  • Currently, you can get_mapped_range() (read access) on a MapMode::Write
  • I am told that writes to write-combining memory can effectively "show up randomly later" to reads
  • Therefore, it is unsound to expose &[u8] pointing to write-combining memory
    • (Unless there is some kind of fence / cache flush that could be used? But we don't need that anyway.)
  • Therefore, we must prohibit get_mapped_range() working on that memory.
  • The simplest API surface we can provide is to say that get_mapped_range() never works on a MapMode::Write mapping under any conditions.
    • This will also simplify the user-facing model because reading a write mapping never was guaranteed to give you meaningful data.

I will update this PR to include a commit that makes this change.

`WriteOnly<'a, T>` is a custom smart pointer which is stricter than
`&'a mut [T]` in that it only allows writing data and not reading it.
This will be part of the fix for <gfx-rs#8897>.

This commit only introduces the public type and does not use it.
Fixes <gfx-rs#8897>.

When accessing a buffer for writing, do not expose (or internally
create) any `&'a mut [u8]` references to that memory, so as to avoid
exposing write-combining memory that might visibly misbehave in atomic
operations. Instead, expose the new pointer type `WriteOnly<'a, [u8]>`
which offers only write operations. This can be sliced and reborrowed,
allowing it to be used in most ways which `&mut [u8]` would be,
hopefully with near-zero overhead.

This pointer type might also allow users to opt in to uninitialized
memory for performance, while confining the consequences to GPU UB
rather than Rust UB. However, that is a future possibility and is not
implemented in this commit other than as a consideration in defining the
API of the `WriteOnly` type.
@cwfitzgerald cwfitzgerald self-assigned this Feb 23, 2026
@inner-daemons inner-daemons removed their request for review February 26, 2026 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BufferViewMut is unsound due to WC memory

4 participants