Lohann opened issue #9579:
Thanks for filing a feature request! Please fill out the TODOs below.
Feature
Hello, I need to access the imported memory inside a functions, then I realized the Caller only show exports, not imports, so I had a lot of trouble to get Wasmtime store working with MaybeUnit, this simple solution segfaults:
pub struct State { pub memory: Memory, } let state = MaybeUninit::<State>::uninit(); let mut store = Store::new(engine, state); let memory_type = MemoryType::new(16, None); store.data_mut().memory.write(Memory::new(&mut store, memory_type)?); let store = unsafe { std::mem::transmute::<Store<MaybeUninit<State>>, Store<State>>(store) }; // segfaults below let instance = Instance::new(&mut store, &module, &imports)?;Then I realized the issue is that there's no way for me transmute only the State, I need to transmute the Store which is not recommended, once rust doesn't guarantee the same memory layout:
assert_eq!(size_of::<Option<bool>>(), 1); assert_eq!(size_of::<Option<MaybeUninit<bool>>>(), 2);Actually I haven't find any way to use MaybeUnit that doesn't look hacky, and I want to avoid the usage of Option and unwraps in the code, once it bloats the binary with panic data.
Alternatives
- Make the imported memory easily available inside Functions, ex: expose it in the Caller.
- Use
#[repr(C)]onStore, so we can safely transmute it.
Lohann edited issue #9579:
Thanks for filing a feature request! Please fill out the TODOs below.
Feature
Hello, I need to access the imported memory inside a functions, then I realized the Caller only show exports, not imports, so I had a lot of trouble to get Wasmtime store working with MaybeUnit, this simple solution segfaults:
pub struct State { pub memory: Memory, } let state = MaybeUninit::<State>::uninit(); let mut store = Store::new(engine, state); let memory_type = MemoryType::new(16, None); store.data_mut().memory.write(Memory::new(&mut store, memory_type)?); let store = unsafe { std::mem::transmute::<Store<MaybeUninit<State>>, Store<State>>(store) }; // ... let imports = [memory.into()]; // segfaults below let instance = Instance::new(&mut store, &module, &imports)?;Then I realized the issue is that there's no way for me transmute only the State, I need to transmute the Store which is not recommended, once rust doesn't guarantee the same memory layout:
assert_eq!(size_of::<Option<bool>>(), 1); assert_eq!(size_of::<Option<MaybeUninit<bool>>>(), 2);Actually I haven't find any way to use MaybeUnit that doesn't look hacky, and I want to avoid the usage of Option and unwraps in the code, once it bloats the binary with panic data.
Alternatives
- Make the imported memory easily available inside Functions, ex: expose it in the Caller.
- Use
#[repr(C)]onStore, so we can safely transmute it.
alexcrichton commented on issue #9579:
Where possible I'd recommend avoiding
unsafe. If things are segfaulting it's probably due to that, so for example you could storeOption<Memory>instead of usingMaybeUninitand then there's no need fortransmuteand this probably won't segfault.Otherwise though is there a problem with storing the memory in
State?
Lohann commented on issue #9579:
Otherwise though is there a problem with storing the memory in State?
There's no problem, is just there's no examples of that, and is not ergonomic as using exported memory, rust encourages the use of Typestate Pattern, where the state of an object guarantees it is valid. In this case I want to guarantee the memory ALWAYS exists, that's why I don't want to use Option.
One example is the NonZeroU32 by knowing the number is never zero, the rust compiler can do some neat optimizations:assert_eq!(size_of::<u32>(), 4); assert_eq!(size_of::<Option<u32>>(), 8); assert_eq!(size_of::<Option<NonZeroU32>>(), 4);In my case I want to guarantee the memory always exists, I don't want to handle the
Option::Noneeverywhere, neither useunwrap()everywhere, that's ok if there's no other option, but I think this is something that should be supported somehow by wasmtime, once the Store owns the memory, makes sense I be able to store it together with the store.
Lohann edited a comment on issue #9579:
Otherwise though is there a problem with storing the memory in State?
There's no problem, but rust encourages the use of Typestate Pattern, where the state of an object guarantees it is valid. In this case I want to guarantee the memory ALWAYS exists, that's why I don't want to use Option.
One example is the NonZeroU32 by knowing the number is never zero, the rust compiler can do some neat optimizations:assert_eq!(size_of::<u32>(), 4); assert_eq!(size_of::<Option<u32>>(), 8); assert_eq!(size_of::<Option<NonZeroU32>>(), 4);In my case I want to guarantee the memory always exists, I don't want to handle the
Option::Noneeverywhere, neither useunwrap()everywhere, that's ok if there's no other option, but I think this is something that should be supported somehow by wasmtime, once the Store owns the memory, makes sense I be able to store it together with the store.
alexcrichton commented on issue #9579:
One thing you could perhaps do is to create a dummy
Memorywith a throwawayStorewhich is placed within futureStores as they're created. That would then be overwritten to the "real" memory once the store is created. That way you can store justMemorywithout having to deal withOptionand you won't have to deal with any unsafety either.
Lohann commented on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to handle imported memory inside imported functions.pub struct State { /// Memory that is IMPORTED to the WASM instance. /// this MUST be here, otherwise you can't read it inside /// imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory that owned by the `store` itself. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State` to be created, but memory also needs `Store` to be created // and the `State` needs memory, so this is a ciclc dependency issue, to solve this by first // create the `State` using a dummy memory with `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized right below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` that replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to handle imported memory inside imported functions.pub struct State { /// Memory that is IMPORTED to the WASM instance. /// this MUST be here, otherwise you can't read it inside /// imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory that owned by the `store` itself. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State` to be created, but memory also needs `Store` to be created // and the `State` needs memory, so this is a ciclc dependency issue, to solve this by first // create the `State` using a dummy memory with `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` that replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to handle imported memory inside imported functions.pub struct State { /// Memory that is IMPORTED to the WASM instance. /// this MUST be here, otherwise you can't read it inside /// imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory that owned by the `store` itself. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State` to be created, but memory also needs `Store` to be created // and the `State` needs memory, so this is a ciclc dependency issue, solved this by first // initialize the `State` with a dummy memory using `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` that replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to handle imported memory inside imported functions.pub struct State { /// Memory that will be IMPORTED to the WASM instance, it must be /// created before instantiate the WASM, and must be stored here /// otherwise you can't read this memory inside imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory that owned by the `store` itself. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State` to be created, but memory also needs `Store` to be created // and the `State` needs memory, so this is a ciclc dependency issue, solved this by first // initialize the `State` with a dummy memory using `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` that replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to handle imported memory inside imported functions.pub struct State { /// Memory that will be IMPORTED to the WASM instance, it must be /// created before instantiate the WASM, and must be stored here /// otherwise you can't read this memory inside imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory that owned by the `store` itself. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State`, the `State` needs `Memory`, but `Memory` can only be // created after `Store`, so we have a ciclc dependency issue. Solved this bellow by first // initialize the `State` with a dummy memory using `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory`, then replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { // Get the address of state's dummy memory. let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); // replaces the dummy memory by a valid memory ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to handle imported memory inside imported functions.pub struct State { /// Memory that will be IMPORTED to the WASM instance, it must be /// created before instantiate the WASM, and must be stored here /// otherwise you can't read this memory inside imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory that owned by the `store` itself. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State`, the `State` needs `Memory`, but `Memory` can only be // created after `Store`, so we have a ciclc dependency issue. Solved this bellow by first // initialize the `State` with a dummy memory using `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` then replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { // Get the address of state's dummy memory. let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); // replaces the dummy memory by a valid memory ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to access imported memory withoutOptionand unsafe code.pub struct State { /// Memory that will be IMPORTED to the WASM instance, it must be /// created before instantiate the WASM, and must be stored here /// otherwise you can't read this memory inside imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory that owned by the `store` itself. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State`, the `State` needs `Memory`, but `Memory` can only be // created after `Store`, so we have a ciclc dependency issue. Solved this bellow by first // initialize the `State` with a dummy memory using `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` then replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { // Get the address of state's dummy memory. let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); // replaces the dummy memory by a valid memory ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to access imported memory withoutOptionor unsafe code.pub struct State { /// Memory that will be IMPORTED to the WASM instance, it must be /// created before instantiate the WASM, and must be stored here /// otherwise you can't read this memory inside imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory that owned by the `store` itself. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State`, the `State` needs `Memory`, but `Memory` can only be // created after `Store`, so we have a ciclc dependency issue. Solved this bellow by first // initialize the `State` with a dummy memory using `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` then replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { // Get the address of state's dummy memory. let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); // replaces the dummy memory by a valid memory ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to access imported memory like exported memory, withoutOptionor unsafe code.pub struct State { /// Memory that will be IMPORTED to the WASM instance, it must be /// created before instantiate the WASM, and must be stored here /// otherwise you can't read this memory inside imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory that owned by the `store` itself. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State`, the `State` needs `Memory`, but `Memory` can only be // created after `Store`, so we have a ciclc dependency issue. Solved this bellow by first // initialize the `State` with a dummy memory using `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` then replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { // Get the address of state's dummy memory. let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); // replaces the dummy memory by a valid memory ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to access imported memory like exported memory, withoutOptionor unsafe code.pub struct State { /// Memory that will be IMPORTED to the WASM instance, then it must be /// created before instantiate the WASM, and must be stored here otherwise /// you can't access the memory inside WasmTime imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory that owned by the `store` itself. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State`, the `State` needs `Memory`, but `Memory` can only be // created after `Store`, so we have a ciclc dependency issue. Solved this bellow by first // initialize the `State` with a dummy memory using `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` then replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { // Get the address of state's dummy memory. let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); // replaces the dummy memory by a valid memory ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited issue #9579:
Feature
Hello, I need to access the imported memory inside a functions, then I realized the Caller only show exports, not imports, so I had a lot of trouble to get Wasmtime store working with MaybeUnit, this simple solution segfaults:
pub struct State { pub memory: Memory, } let state = MaybeUninit::<State>::uninit(); let mut store = Store::new(engine, state); let memory_type = MemoryType::new(16, None); store.data_mut().memory.write(Memory::new(&mut store, memory_type)?); let store = unsafe { std::mem::transmute::<Store<MaybeUninit<State>>, Store<State>>(store) }; // ... let imports = [memory.into()]; // segfaults below let instance = Instance::new(&mut store, &module, &imports)?;Then I realized the issue is that there's no way for me transmute only the State, I need to transmute the Store which is not recommended, once rust doesn't guarantee the same memory layout:
assert_eq!(size_of::<Option<bool>>(), 1); assert_eq!(size_of::<Option<MaybeUninit<bool>>>(), 2);Actually I haven't find any way to use MaybeUnit that doesn't look hacky, and I want to avoid the usage of Option and unwraps in the code, once it bloats the binary with panic data.
Alternatives
- Make the imported memory easily available inside Functions, ex: expose it in the Caller.
- Use
#[repr(C)]onStore, so we can safely transmute it.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to access imported memory withoutOptionorunsafecode.pub struct State { /// Memory that will be IMPORTED to the WASM instance, then it must be /// created before instantiate the WASM, and must be stored here otherwise /// you can't access the memory inside WasmTime imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory that owned by the `store` itself. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State`, the `State` needs `Memory`, but `Memory` can only be // created after `Store`, so we have a ciclc dependency issue. Solved this bellow by first // initialize the `State` with a dummy memory using `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` then replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { // Get the address of state's dummy memory. let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); // replaces the dummy memory by a valid memory ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to access imported memory withoutOptionorunsafecode.pub struct State { /// Memory that will be IMPORTED to the WASM instance, then it must be /// created before instantiate the WASM, and must be stored here otherwise /// you can't access the memory inside WasmTime imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory owned by the `store`. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State`, the `State` needs `Memory`, but `Memory` can only be // created after `Store`, so we have a ciclc dependency issue. Solved this bellow by first // initialize the `State` with a dummy memory using `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` then replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { // Get the address of state's dummy memory. let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); // replaces the dummy memory by a valid memory ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
Lohann edited a comment on issue #9579:
Update: I managed to store the imported memory in the
StatewithoutOption<Memory>by using MaybeUninit, the code is sound but requires two unsafe blocks.For me it worthy the price, better than having to handle
Option<Memory>in every imported function. But I still would love to have a more ergonomic way to access imported memory withoutOptionorunsafecode.pub struct State { /// Memory that will be IMPORTED to the WASM instance, then it must be /// created before instantiate the WASM, must be stored here otherwise /// you can't access the memory inside WasmTime imported functions. pub memory: Memory, } impl State { /// Initializes a Store with a memory owned by the `store`. pub fn new(engine: &Engine, memory_type: MemoryType) -> anyhow::Result<Store<Self>> { // The `Store` needs `State`, the `State` needs `Memory`, but `Memory` can only be // created after `Store`, so we have a ciclc dependency issue. Solved this bellow by first // initialize the `State` with a dummy memory using `MaybeUninit::zeroed().assume_init()`. // References: // - https://github.com/bytecodealliance/wasmtime/issues/4922#issuecomment-1251086171 // - https://doc.rust-lang.org/std/mem/union.MaybeUninit.html #[allow(clippy::uninit_assumed_init)] let state = Self { // SAFETY: This `memory` will be correctly initialized after the `Store` below. memory: unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }, }; // Now use the `State` to create the `Store`. let mut store = Store::new(engine, state); // Use the `Store` to create a valid `Memory` then replaces the dummy memory. // Ref: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-a-struct-field-by-field unsafe { // Get the address of state's dummy memory. let ptr = std::ptr::addr_of_mut!(store.data_mut().memory); // replaces the dummy memory by a valid memory ptr.write(Memory::new(&mut store, memory_type)?); } Ok(store) } }Before you ask, no, I can't simply export the memory like everyone else, because the wasm code is a ink smart-contract, and the memory is always imported.
alexcrichton added the wasmtime:api label to Issue #9579.
ZylosLumen commented on issue #9579:
Just ran into this issue and wanted to avoid
unsafe Option::unwrap_unchecked()everywhere but it will have to do for now.
Really curious as importing the memory (or any other store-bound type) and also needing to access it from the host does not seem like a rare use case so having a low cost api would be really nice
Lohann commented on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it doesn't have lifetime and implementsCopy), so one possible solutions is create the store and memory atomically, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<_>| -> State { State { memory: Memory::new(store_ref, memory_type) } });Then need to figure what type the
store_refwill be.. it can't beStore<State>.Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .with_state(|linker|: Option<State> { let memory = linker.get("env", "memory")?; Some(State { memory }) }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it doesn't have lifetime and implementsCopy), so one possible solutions is create the store and memory atomically, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<()>| -> State { State { memory: Memory::new(store_ref, memory_type) } });Then need to figure what type the
store_refwill be.. it can't beStore<State>.Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .with_state(|linker|: Option<State> { let memory = linker.get("env", "memory")?; Some(State { memory }) }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it doesn't have lifetime and implementsCopy), so one possible solutions is create the store and memory atomically, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<()>| -> State { State { memory: Memory::new(store_ref, memory_type) } });Then need to figure what type the
store_refwill be.. it can't beStore<State>.Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .init_state(|linker|: State { // SAFETY: the memory was imported above let memory = linker.get("env", "memory").unwrap(); State { memory } }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it implementsCopy), so one possible solutions is create the store and memory atomically, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<()>| -> State { State { memory: Memory::new(store_ref, memory_type) } });Then need to figure what type the
store_refwill be.. it can't beStore<State>.Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .init_state(|linker|: State { // SAFETY: the memory was imported above let memory = linker.get("env", "memory").unwrap(); State { memory } }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it implementsCopy), so a possible solution is create both the state and memory atomically, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<()>| -> State { State { memory: Memory::new(store_ref, memory_type) } });Then need to figure what type the
store_refwill be.. it can't beStore<State>.Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .init_state(|linker|: State { // SAFETY: the memory was imported above let memory = linker.get("env", "memory").unwrap(); State { memory } }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it implementsCopy), so a possible solution is create both the state and memory atomically, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<()>| -> State { State { memory: Memory::new(store_ref, memory_type) } });Maybe
store_refcan be another type that creates the memory instead being a reference to a dummy store.Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .init_state(|linker|: State { // SAFETY: the memory was imported above let memory = linker.get("env", "memory").unwrap(); State { memory } }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it implementsCopy), so a possible solution is create both the state and memory atomically, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<()>| -> State { State { memory: Memory::new(store_ref, memory_type) } });Or maybe
store_refcan be another type able to create the memory without expose the store:let mut store = Store::lazy_init(engine, move |linker: &mut LazyLinker<State>| -> State { let memory_type = MemoryType::new(16, None); let memory: Memory = linker.lazy_memory("env", "memory", memory_type); State { memory } })?;Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .init_state(|linker|: State { // SAFETY: the memory was imported above let memory = linker.get("env", "memory").unwrap(); State { memory } }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it implementsCopy), so a possible solution is create both the state and memory atomically, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<()>| -> State { State { memory: Memory::new(store_ref, memory_type) } });Or maybe instead
Store<()>, we provide some way to create the memory without expose the store:let mut store: Store<State> = Store::lazy_init(engine, move |linker: &mut LazyLinker<State>| -> State { let memory_type = MemoryType::new(16, None); let memory: Memory = linker.lazy_memory("env", "memory", memory_type); State { memory } })?;Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .init_state(|linker|: State { // SAFETY: the memory was imported above let memory = linker.get("env", "memory").unwrap(); State { memory } }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it implementsCopy), so a possible solution is create both the state and memory atomically, or create the Store last, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<()>| -> State { State { memory: Memory::new(store_ref, memory_type) } });Or maybe instead
Store<()>, we provide some way to create the memory without expose the store:let mut store: Store<State> = Store::lazy_init(engine, move |linker: &mut LazyLinker<State>| -> State { let memory_type = MemoryType::new(16, None); let memory: Memory = linker.lazy_memory("env", "memory", memory_type); State { memory } })?;Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .init_state(|linker|: State { // SAFETY: the memory was imported above let memory = linker.get("env", "memory").unwrap(); State { memory } }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it implementsCopy), so a possible solution is create both the state and memory atomically, or create the Store last, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<MaybeUnit<State>>| -> State { // obs: in method requires `State` to implement Unpin, this prevents State from reference Store. State { memory: Memory::new(store_ref, memory_type) } });Or maybe instead
Store<()>, we provide some way to create the memory without expose the store:let mut store: Store<State> = Store::lazy_init(engine, move |linker: &mut LazyLinker<State>| -> State { let memory_type = MemoryType::new(16, None); State { memory } })?;Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .init_state(|linker|: State { // SAFETY: the memory was imported above let memory = linker.get("env", "memory").unwrap(); State { memory } }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it implementsCopy), so a possible solution is create both the state and memory atomically, or create the Store last, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<MaybeUnit<State>>| -> State { // obs: in method requires `State` to implement Unpin, this prevents State from reference Store. State { memory: Memory::new(store_ref, memory_type) } });Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .init_state(|linker|: State { // SAFETY: the memory was imported above let memory = linker.get("env", "memory").unwrap(); State { memory } }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it implementsCopy), so a possible solution is create both the state and memory atomically, or create the Store last, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<Opaque>| -> State { // obs: in method requires `State` to implement Unpin, this prevents State from reference Store. State { memory: Memory::new(store_ref, memory_type) } });Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .init_state(|linker|: State { // SAFETY: the memory was imported above let memory = linker.get("env", "memory").unwrap(); State { memory } }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
Lohann edited a comment on issue #9579:
@alexcrichton
I noticed Memory is simple a identifier, it doesn't have the actual buffer, (that's why it implementsCopy), so a possible solution is create both the state and memory atomically, or create the Store last, ex:Proposal 1 -
Store::new_withpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let mut store = Store::new_with(engine, move |store_ref: &mut Store<_>| -> State { // obs: in method requires `State` to implement Unpin, this prevents State from reference Store. State { memory: Memory::new(store_ref, memory_type) } });Proposal 2 -
StoreBuilderpub struct State { pub memory: Memory, } let memory_type = MemoryType::new(16, None); let (mut store, mut linker): = StoreBuilder::new() .with_memory("env", "memory", memory_type) .with_func("env", "add", |x: i32, y: i32| x + y) .init_state(|linker|: State { // SAFETY: the memory was imported above let memory = linker.get("env", "memory").unwrap(); State { memory } }) .build()?; let instance = linker.instantiate(&mut store, &module)?;
alexcrichton commented on issue #9579:
Personally I would recommend having a
memory()function as opposed amemory.unwrap()field access for ergonomics. I'll also note thatunsafe { MaybeUninit::<Memory>::zeroed().assume_init() }is immediate undefined behavior.Memoryhas aNonZeroU64inside of it which this is explicitly setting to zero. Whether or not that actually causes a problem is entirely up to LLVM which is highly likely to change over time.I would recommend measuring the overhead of doing
.unwrap()before doing anythingunsafew.r.t. this issue. It is an incredibly high bar to clear, in my opinion, to save a few instructions while addingunsafecode.In terms of "solving" this issue, personally I feel at this time that it's pretty unlikely. The APIs you're sketching out @Lohann for example are not possible to implement in a sound fashion. Once a
Store<T>is presented, for example, then instances can be created which can execute wasm which can execute imports which expect functions to be there. The validity of this pattern requires the inability to invoke imported functions (or accessTin general) before theTis fully filled in. That's, in general, a dynamic property of the embedding in question rather than one we can bake into the type system of Wasmtime.
Last updated: Dec 13 2025 at 19:03 UTC