Hello,
I would like to use epoch-based interruption with wasmtime but my target doesn't have 64 bits atomics. I would like to add support for this in the wasmtime. There are two orthogonal questions on which I would like feedback before trying to contribute anything
core::sync::atomic or is it ok to use the portable-atomics crate's atomic types. For the first question, this would increase mean writing stuff like :
#[cfg( target_has_atomic = "64")]
epoch: AtomicU64,
#[cfg(all(feature = "runtime", target_has_atomic = "32", not(target_has_atomic = "64")))]
epoch: AtomicU32,
// On every function previously #[cfg( target_has_atomic = "64")]
#[cfg(any(target_has_atomic = "64", target_has_atomic = "32"))]
fn a() {}
And documenting that the maximum number of epochs isn't the same for every platform.
Using portable-atomic would allow every platform that supports portable-atomic to use an AtomicU64. This would most likely require adding a new portable-atomic feature to wasmtime that enables this dependency and uses its atomic types instead of the ones provided by core.
Would using fuel work for your use case? Epochs get pretty tricky without 64-bit atomics unfortunately. One of the difficulties is plumbing this support through not only #[cfg] but through. Cranelift as well to understand it's a 32-bit variable instead of a 64-bit variable
Then using portable-atomic would be more suited than lowering the size of the atomics.
I could probably use fuel provided that it works well with async but I'm worried about the increased code size from the fuel instrumentation
Oh that'd be an interesting point of comparison yeah, code size with/without fuel. If the code size of fuel vs epochs is a tipping point it's honestly probably easier to make the code size for fuel smaller (e.g. I assume you're using Pulley so we could start adding some custom opcodes)
Otherwise though one way we could manage #[cfg] is to, in theory, have:
#[cfg(long_expr)]
mod epoch64;
#[cfg(not(long_expr), longer_expr)]
mod epoch32;
#[cfg(not(long_expr), not(longer_expr))]
mod epoch_disabled;
or something like that, basically only having #[cfg] at the module level and stuffing it all in there
I tried to precompile the same component with the following options :
config.memory_reservation(0);
config.wasm_custom_page_sizes(true);
config.memory_may_move(false);
config.memory_init_cow(false);
config.cranelift_opt_level(wasmtime::OptLevel::SpeedAndSize);
config.target("pulley32");
6262 bytes19952 bytes 24456bytes (122% of the un-instrumented code)26528 bytes (132% of the un-instrumented code)If using the portable-atomic it could also be as simple as changing this in engine.rs
use core::sync::atomic::Ordering;
#[cfg(target_has_atomic = "64")]
use core::sync::atomic::AtomicU64;
#[cfg(feature = "portable-atomic")]
use portable_atomic::AtomicU64;
Or am I missing something with regards to Cranelift ?
I'm not really sure, I don't know how portable-atomic is itself implemented
I'd have to dig into how an atomic 64-bit read works, because for example that's what Cranelift does and whatever portable-atomic does would need to be reflected into Cranelift too
Last updated: Dec 06 2025 at 06:05 UTC