V0ldek opened issue #7946:
I am trying to write my own
LinearMemoryimplementation. The issue that I am facing is that it appears theas_ptrfunction is inherently incompatible with theSynctrait bound.In essence,
as_ptrallows interior mutability, since it's a&selffunction that returns amutpointer to data owned by the memory. Interior mutability is explicitly notSync, though.Assume
T: LinearMemory. It must also beSync, so&Tmust be safely shareable between threads. However, then two threads can callas_ptr, _which is a safe function_, and obtain*mut T. At that point we're sharing mutable pointers concurrently and all bets are off. So any usefulTcannot beSyncin my mind.As far as I can grok what the runtime is doing with the memory, it only uses
LinearMemoryimplementations from a single thread as long as wasm threading andSharedMemoryis not involved. So it kinda seems like I am required to lie to the Rust compiler that my type isSyncand then trustwasmtimethat it will not do anything nasty with it.I looked at the implementation of
MmapMemoryused by the actual runtime, and underneath insys::unix::mmap::Mmapyou just wrap the raw pointer inSendSyncPtrand pretend it'sSync, so I am assuming that this is what would be expected from someone implementingLinearMemoryfrom the outside as well.Here are my questions:
- First, am I even correct above or is there something I'm missing?
- Could we get this documented in
LinearMemoryto guide implementors? For example, is it correct to assume thatas_ptrwill never be called from different threads on the same instance if wasm threads and shared memory are not involved? If yes, then at least I can rest assured that as long as I use myT: LinearMemoryonly in a single-threaded context with single-threadedwasmtimenothing bad will happen.- Can this
Syncrequirement be lifted altogether? It seems that sinceLinearMemoryinherently requires interior mutability it'd make sense for it to _not_ beSync, and instead havewasmtimehandle it unsafely itself, for example by wrapping it in a type withunsafe impl Syncthat is internal to the runtime. That way it'd guaranteed that thread madness can only happen insidewasmtime.
V0ldek edited issue #7946:
I am trying to write my own
LinearMemoryimplementation. The issue that I am facing is that it appears theas_ptrfunction is inherently incompatible with theSynctrait bound. In essence,as_ptrallows interior mutability, since it's a&selffunction that returns amutpointer to data owned by the memory. Interior mutability is explicitly notSync, though.Assume
T: LinearMemory. It must also beSync, so&Tmust be safely shareable between threads. However, then two threads can callas_ptr, _which is a safe function_, and obtain*mut T. At that point we're sharing mutable pointers concurrently and all bets are off. So any usefulTcannot beSyncin my mind.As far as I can grok what the runtime is doing with the memory, it only uses
LinearMemoryimplementations from a single thread as long as wasm threading andSharedMemoryis not involved. So it kinda seems like I am required to lie to the Rust compiler that my type isSyncand then trustwasmtimethat it will not do anything nasty with it.I looked at the implementation of
MmapMemoryused by the actual runtime, and underneath insys::unix::mmap::Mmapyou just wrap the raw pointer inSendSyncPtrand pretend it'sSync, so I am assuming that this is what would be expected from someone implementingLinearMemoryfrom the outside as well.Here are my questions:
- First, am I even correct above or is there something I'm missing?
- Could we get this documented in
LinearMemoryto guide implementors? For example, is it correct to assume thatas_ptrwill never be called from different threads on the same instance if wasm threads and shared memory are not involved? If yes, then at least I can rest assured that as long as I use myT: LinearMemoryonly in a single-threaded context with single-threadedwasmtimenothing bad will happen.- Can this
Syncrequirement be lifted altogether? It seems that sinceLinearMemoryinherently requires interior mutability it'd make sense for it to _not_ beSync, and instead havewasmtimehandle it unsafely itself, for example by wrapping it in a type withunsafe impl Syncthat is internal to the runtime. That way it'd guaranteed that thread madness can only happen insidewasmtime.
V0ldek commented on issue #7946:
BTW the "
MemorySafety and Threads" section starts withCurrently the wasmtime crate does not implement the wasm threads proposal, but it is planned to do so.
I think this is outdated now, there is threads support :)
alexcrichton commented on issue #7946:
First, am I even correct above or is there something I'm missing?
You're both correct and incorrect a bit, I can try to help clear this up a bit. I'll note that I'm no expert in
unsafeRust so what follows is my own personal understanding. I may have some exact specifics slightly off, but I think the high-level is right.To Rust
*const Tand*mut Tare the same in terms of semantic guarantees. You can read from both and while the compiler requires*mut Tto write you can safely cast*const Tto*mut Tso you can sort of write through a*const Tas well. In that sense when you say:At that point we're sharing mutable pointers concurrently and all bets are off
I believe that this is incorrect. If we were talking about
&mut Tthen I believe your statement is correct, but*mut Tis different in this regard.The way I sort of think of it is that
&mut Tis statically safe and*mut Tmust be "runtime safe". When a*mut Tis mutated it must, at that time, be the only mutator. When*mut Tisn't actually being accessed though you can have as many floating around as you'd like.Could we get this documented in LinearMemory to guide implementors?
Definitely makes sense to me to improve the documentation here!
For example, is it correct to assume that as_ptr will never be called from different threads on the same instance if wasm threads and shared memory are not involved?
To answer this: probably not. The
Memory::dataAPI only requires&Store<T>which means that it can be called concurrently on many threads. This exact API does not literally callLinearMemory::as_ptrbut it theoretically could from the runtime's perspective. So you shouldn't rely on being only called on one thread at a time, even when wasm threads aren't involved.Note, though, that you can create
*mut u8from&[u8]viafoo.as_ptr().cast_mut()in safe Rust.Can this Sync requirement be lifted altogether?
No.
The reasoning here is a little nuanced, but the main idea is that everything about
SendandSyncis required to make anything aboutwasmtime::Store<T>bothSendandSync. Note, however, thatSyncdoes not mean "no interior mutability" nor "no mutation". For exampleVec<T>isSyncdespite allowing mutation, explicitly because all mutation requires&mut T. Also note that types likeMutex<T>andAtomicUsizeare bothSyncwhile allowing interior mutation.
That's a lot of words, but my hunch is it won't be the most satisfying answer to you. I'm more than happy to keep answering questions though! We can also chat on Zulip for something a little less async if you'd like too.
V0ldek commented on issue #7946:
Hey, thanks for the response :) I prefer async comms for now since it takes me quite a bit of time to formulate these points coherently - this stuff is hard!
I think you're technically right about raw pointers being special. They're not
Sync/Sendmore as a lint than anything else, simply because actually doing anything with a pointer (read/write) requiresunsafeanyway.Note, however, that Sync does not mean "no interior mutability" nor "no mutation". For example Vec<T> is Sync despite allowing mutation, explicitly because all mutation requires &mut T. Also note that types like Mutex<T> and AtomicUsize are both Sync while allowing interior mutation.
Yes, of course, but that is precisely my point -- the thing that makes these types safe is that they don't have an API that's just "turn a
&selfinto a mutable pointer". For example,Mutexgives you a guard whose lifetime is the same as&selfthat ensures thread-safety. But doing something like that withLinearMemoryis impossible. I cannot, for example, havestruct T { mem: RwLock<*mut u8> }implementLinearMemory, because theas_ptrfunction won't allow me to take a lock and return the guard.Let me rephrase my questions to hopefully make this discussion productive :) I'm mostly interested in being able to be reasonable sure that my implementation of
LinearMemoryisn't going to cause undefined behaviour. Here is what _I believe_ to be the complete list of things that might cause multiple threads to mutably access myT: LinearMemory:
- I use
Tin my code myself and share it between threads and then do nasty stuff with it.- I use
wasmtimewith multi-threaded wasm code, in which case obviously thread-unsafety in the wasm being ran will result in thread-unsafety in the shared memory.- I misuse the
MemoryAPI in my own code, for example holding mutable accesses across wasm calls, or other things that are already listed in theMemorydocs as unsafe.Crucially, note that 1. and 3. are completely "my problems", i.e. I can audit my own code to detect violations of those. Point 2. is a natural consequence of running arbitrary wasm code, but it also carries an implication that if I were to audit all wasm code that runs I would prevent violations.
In other words it'd be good to get some strong guarantee from
wasmtimeabout how exactly the memory will be used, and put it explicitly onLinearMemorydocs. Then I could audit my code for violations of the contract and be relatively certain there won't be UB creeping up.
V0ldek edited a comment on issue #7946:
Hey, thanks for the response :) I prefer async comms for now since it takes me quite a bit of time to formulate these points coherently - this stuff is hard!
I think you're technically right about raw pointers being special. They're not
Sync/Sendmore as a lint than anything else, simply because actually doing anything with a pointer (read/write) requiresunsafeanyway.Note, however, that Sync does not mean "no interior mutability" nor "no mutation". For example Vec<T> is Sync despite allowing mutation, explicitly because all mutation requires &mut T. Also note that types like Mutex<T> and AtomicUsize are both Sync while allowing interior mutation.
Yes, of course, but that is precisely my point -- the thing that makes these types safe is that they don't have an API that's just "turn a
&selfinto a mutable pointer". For example,Mutexgives you a guard whose lifetime is the same as&selfthat ensures thread-safety. But doing something like that withLinearMemoryis impossible. I cannot, for example, havestruct T { mem: RwLock<*mut u8> }implementLinearMemory, because theas_ptrfunction won't allow me to take a lock and return the guard.Let me rephrase my questions to hopefully make this discussion productive :) I'm mostly interested in being able to be reasonable sure that my implementation of
LinearMemoryisn't going to cause undefined behaviour. Here is what _I believe_ to be the complete list of things that might cause multiple threads to mutably access myT: LinearMemory:
- I use
Tin my code myself and share&Tbetween threads and then do nasty stuff with it.- I use
wasmtimewith multi-threaded wasm code, in which case obviously thread-unsafety in the wasm being ran will result in thread-unsafety in the shared memory.- I misuse the
MemoryAPI in my own code, for example holding mutable accesses across wasm calls, or other things that are already listed in theMemorydocs as unsafe.Crucially, note that 1. and 3. are completely "my problems", i.e. I can audit my own code to detect violations of those. Point 2. is a natural consequence of running arbitrary wasm code, but it also carries an implication that if I were to audit all wasm code that runs I would prevent violations.
In other words it'd be good to get some strong guarantee from
wasmtimeabout how exactly the memory will be used, and put it explicitly onLinearMemorydocs. Then I could audit my code for violations of the contract and be relatively certain there won't be UB creeping up.
alexcrichton commented on issue #7946:
These are good points, and if you're up for it I'd be happy to review a PR of docs for
LinearMemory! Reviewing some code we may not actually end up usingLinearMemoryimplementations for wasm shared memory with wasm threads, but that's sort of a bug in Wasmtime where the intention is that they're still used. I think this is just an oversight.Otherwise though you're correct that (2) is the main Wasmtime-related thing to guarantee here, and yes custom memories when used with wasm threads can be mutated by wasm in multiple threads. It might be worth clarifying though that
as_ptr(&self) -> *mut T, when called, does not represent an intent-to-mutate. It's possible to mutate withunsafecode but part of that contract of theunsafeis that it's done safely with respect to other mutations.What I can say, though, is that if you're not dealing with shared memory then, yes, Wasmtime guarantees that mutations to memory will happen in at most one thread at a time. That's guaranteed by the nature of requiring
&mut Store<T>whenever the memory is mutated and that mutable borrow serves as proof of "I'm the thread allowed to mutate right now"
Last updated: Dec 13 2025 at 21:03 UTC