diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs index a2ec33a9f5fd..bb4f6ed551ba 100644 --- a/crates/bench-api/src/lib.rs +++ b/crates/bench-api/src/lib.rs @@ -522,7 +522,7 @@ impl BenchState { store.set_epoch_deadline(1); } if let Some(fuel) = self.fuel { - store.add_fuel(fuel).unwrap(); + store.set_fuel(fuel).unwrap(); } let instance = self.linker.instantiate(&mut store, &module)?; diff --git a/crates/c-api/include/wasmtime/async.h b/crates/c-api/include/wasmtime/async.h index 955944c72086..1b490003a3ab 100644 --- a/crates/c-api/include/wasmtime/async.h +++ b/crates/c-api/include/wasmtime/async.h @@ -71,19 +71,24 @@ WASMTIME_CONFIG_PROP(void, async_support, bool) WASMTIME_CONFIG_PROP(void, async_stack_size, uint64_t) /** - * \brief Configures a Store to yield execution of async WebAssembly code periodically. + * \brief Configures a Store to yield execution of async WebAssembly code + * periodically. * - * When a Store is configured to consume fuel with `wasmtime_config_consume_fuel` - * this method will configure what happens when fuel runs out. Specifically executing - * WebAssembly will be suspended and control will be yielded back to the caller. + * When a Store is configured to consume fuel with + * `wasmtime_config_consume_fuel` this method will configure what happens when + * fuel runs out. Specifically executing WebAssembly will be suspended and + * control will be yielded back to the caller. * - * This is only suitable with use of a store associated with an async config because - * only then are futures used and yields are possible. + * This is only suitable with use of a store associated with an async config + * because only then are futures used and yields are possible. + * + * \param context the context for the store to configure. + * \param interval the amount of fuel at which to yield. A value of 0 will + * disable yielding. */ -WASM_API_EXTERN void wasmtime_context_out_of_fuel_async_yield( - wasmtime_context_t *context, - uint64_t injection_count, - uint64_t fuel_to_inject); +WASM_API_EXTERN void +wasmtime_context_fuel_async_yield_interval(wasmtime_context_t *context, + uint64_t interval); /** * \brief Configures epoch-deadline expiration to yield to the async caller and the update the deadline. @@ -94,7 +99,7 @@ WASM_API_EXTERN void wasmtime_context_out_of_fuel_async_yield( * See the Rust documentation for more: * https://docs.wasmtime.dev/api/wasmtime/struct.Store.html#method.epoch_deadline_async_yield_and_update */ -WASM_API_EXTERN void wasmtime_context_epoch_deadline_async_yield_and_update( +WASM_API_EXTERN wasmtime_error_t* wasmtime_context_epoch_deadline_async_yield_and_update( wasmtime_context_t *context, uint64_t delta); @@ -170,8 +175,8 @@ typedef struct wasmtime_call_future wasmtime_call_future_t; * called again for a given future. * * This function returns false if execution has yielded either due to being out of fuel - * (see wasmtime_store_out_of_fuel_async_yield), or the epoch has been incremented enough - * (see wasmtime_store_epoch_deadline_async_yield_and_update). The function may also return false if + * (see wasmtime_context_fuel_async_yield_interval), or the epoch has been incremented enough + * (see wasmtime_context_epoch_deadline_async_yield_and_update). The function may also return false if * asynchronous host functions have been called, which then calling this function will call the * continuation from the async host function. * diff --git a/crates/c-api/include/wasmtime/store.h b/crates/c-api/include/wasmtime/store.h index ac67d1a1dd82..740d98a339f5 100644 --- a/crates/c-api/include/wasmtime/store.h +++ b/crates/c-api/include/wasmtime/store.h @@ -147,25 +147,6 @@ WASM_API_EXTERN void wasmtime_context_set_data(wasmtime_context_t* context, void */ WASM_API_EXTERN void wasmtime_context_gc(wasmtime_context_t* context); -/** - * \brief Adds fuel to this context's store for wasm to consume while executing. - * - * For this method to work fuel consumption must be enabled via - * #wasmtime_config_consume_fuel_set. By default a store starts with 0 fuel - * for wasm to execute with (meaning it will immediately trap). - * This function must be called for the store to have - * some fuel to allow WebAssembly to execute. - * - * Note by default when fuel is entirely consumed it will cause - * wasm to trap. If async_support is enabled, you can use - * #wasmtime_context_out_of_fuel_async_yield and the async APIs to yield - * execution instead. - * - * If fuel is not enabled within this store then an error is returned. If fuel - * is successfully added then NULL is returned. - */ -WASM_API_EXTERN wasmtime_error_t *wasmtime_context_add_fuel(wasmtime_context_t *store, uint64_t fuel); - /** * \brief Set fuel to this context's store for wasm to consume while executing. * @@ -175,55 +156,24 @@ WASM_API_EXTERN wasmtime_error_t *wasmtime_context_add_fuel(wasmtime_context_t * * This function must be called for the store to have * some fuel to allow WebAssembly to execute. * - * Note by default when fuel is entirely consumed it will cause - * wasm to trap. If async_support is enabled, you can use - * #wasmtime_context_out_of_fuel_async_yield and the async APIs to yield - * execution instead. + * Note that when fuel is entirely consumed it will cause wasm to trap. * * If fuel is not enabled within this store then an error is returned. If fuel * is successfully added then NULL is returned. */ -WASM_API_EXTERN wasmtime_error_t *wasmtime_context_reset_fuel(wasmtime_context_t *store, uint64_t fuel); +WASM_API_EXTERN wasmtime_error_t *wasmtime_context_set_fuel(wasmtime_context_t *store, uint64_t fuel); /** - * \brief Returns the amount of fuel consumed by this context's store execution - * so far. + * \brief Returns the amount of fuel remaining in this context's store. * * If fuel consumption is not enabled via #wasmtime_config_consume_fuel_set - * then this function will return false. Otherwise true is returned and the + * then this function will return an error. Otherwise `NULL` is returned and the * fuel parameter is filled in with fuel consumed so far. * * Also note that fuel, if enabled, must be originally configured via - * #wasmtime_context_add_fuel. - */ -WASM_API_EXTERN bool wasmtime_context_fuel_consumed(const wasmtime_context_t *context, uint64_t *fuel); - -/** - * \brief Returns the amount of fuel remaining in this context's store execution - * before engine traps execution. - * - * If fuel consumption is not enabled via #wasmtime_config_consume_fuel_set - * then this function will return false. Otherwise true is returned and the - * fuel parameter is filled in with remaining fuel. - * - * Also note that fuel, if enabled, must be originally configured via - * #wasmtime_context_add_fuel. - */ -WASM_API_EXTERN bool wasmtime_context_fuel_remaining(const wasmtime_context_t *context, uint64_t *fuel); - -/** - * \brief Attempt to manually consume fuel from the store. - * - * If fuel consumption is not enabled via #wasmtime_config_consume_fuel_set then - * this function will return an error. Otherwise this will attempt to consume - * the specified amount of `fuel` from the store. If successful the remaining - * amount of fuel is stored into `remaining`. If `fuel` couldn't be consumed - * then an error is returned. - * - * Also note that fuel, if enabled, must be originally configured via - * #wasmtime_context_add_fuel. + * #wasmtime_context_set_fuel. */ -WASM_API_EXTERN wasmtime_error_t *wasmtime_context_consume_fuel(wasmtime_context_t *context, uint64_t fuel, uint64_t *remaining); +WASM_API_EXTERN wasmtime_error_t* wasmtime_context_get_fuel(const wasmtime_context_t *context, uint64_t *fuel); /** * \brief Configures WASI state within the specified store. diff --git a/crates/c-api/src/async.rs b/crates/c-api/src/async.rs index 60cf7d30843e..ffd9b10c1048 100644 --- a/crates/c-api/src/async.rs +++ b/crates/c-api/src/async.rs @@ -1,6 +1,7 @@ use std::ffi::c_void; use std::future::Future; use std::mem::{self, MaybeUninit}; +use std::num::NonZeroU64; use std::ops::Range; use std::pin::Pin; use std::sync::Arc; @@ -36,12 +37,14 @@ pub extern "C" fn wasmtime_context_epoch_deadline_async_yield_and_update( } #[no_mangle] -pub extern "C" fn wasmtime_context_out_of_fuel_async_yield( +pub extern "C" fn wasmtime_context_fuel_async_yield_interval( mut store: CStoreContextMut<'_>, - injection_count: u64, - fuel_to_inject: u64, -) { - store.out_of_fuel_async_yield(injection_count, fuel_to_inject); + interval: Option, +) -> Option> { + handle_result( + store.fuel_async_yield_interval(interval.map(|n| n.get())), + |()| {}, + ) } pub type wasmtime_func_async_callback_t = extern "C" fn( diff --git a/crates/c-api/src/store.rs b/crates/c-api/src/store.rs index d6c95e00cbd3..844357fb2f8f 100644 --- a/crates/c-api/src/store.rs +++ b/crates/c-api/src/store.rs @@ -209,54 +209,20 @@ pub extern "C" fn wasmtime_context_gc(mut context: CStoreContextMut<'_>) { } #[no_mangle] -pub extern "C" fn wasmtime_context_add_fuel( +pub extern "C" fn wasmtime_context_set_fuel( mut store: CStoreContextMut<'_>, fuel: u64, ) -> Option> { - crate::handle_result(store.add_fuel(fuel), |()| {}) + crate::handle_result(store.set_fuel(fuel), |()| {}) } #[no_mangle] -pub extern "C" fn wasmtime_context_reset_fuel( - mut store: CStoreContextMut<'_>, - fuel: u64, -) -> Option> { - crate::handle_result(store.reset_fuel(fuel), |()| {}) -} - -#[no_mangle] -pub extern "C" fn wasmtime_context_fuel_consumed(store: CStoreContext<'_>, fuel: &mut u64) -> bool { - match store.fuel_consumed() { - Some(amt) => { - *fuel = amt; - true - } - None => false, - } -} - -#[no_mangle] -pub extern "C" fn wasmtime_context_fuel_remaining( - store: CStoreContextMut<'_>, +pub extern "C" fn wasmtime_context_get_fuel( + store: CStoreContext<'_>, fuel: &mut u64, -) -> bool { - match store.fuel_remaining() { - Some(remaining) => { - *fuel = remaining; - true - } - None => false, - } -} - -#[no_mangle] -pub extern "C" fn wasmtime_context_consume_fuel( - mut store: CStoreContextMut<'_>, - fuel: u64, - remaining_fuel: &mut u64, ) -> Option> { - crate::handle_result(store.consume_fuel(fuel), |remaining| { - *remaining_fuel = remaining; + crate::handle_result(store.get_fuel(), |amt| { + *fuel = amt; }) } diff --git a/crates/fuzzing/src/generators/config.rs b/crates/fuzzing/src/generators/config.rs index 5a29e689fc2d..3f3255f0382a 100644 --- a/crates/fuzzing/src/generators/config.rs +++ b/crates/fuzzing/src/generators/config.rs @@ -256,7 +256,7 @@ impl Config { pub fn configure_store(&self, store: &mut Store) { store.limiter(|s| s as &mut dyn wasmtime::ResourceLimiter); if self.wasmtime.consume_fuel { - store.add_fuel(u64::max_value()).unwrap(); + store.set_fuel(u64::MAX).unwrap(); } if self.wasmtime.epoch_interruption { // Without fuzzing of async execution, we can't test the diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs index d154904204b0..fc0eb26594f1 100644 --- a/crates/fuzzing/src/oracles.rs +++ b/crates/fuzzing/src/oracles.rs @@ -146,7 +146,7 @@ pub fn instantiate(wasm: &[u8], known_valid: bool, config: &generators::Config, let mut timeout_state = SignalOnDrop::default(); match timeout { - Timeout::Fuel(fuel) => set_fuel(&mut store, fuel), + Timeout::Fuel(fuel) => store.set_fuel(fuel).unwrap(), // If a timeout is requested then we spawn a helper thread to wait for // the requested time and then send us a signal to get interrupted. We @@ -599,7 +599,7 @@ pub fn table_ops( { fuzz_config.wasmtime.consume_fuel = true; let mut store = fuzz_config.to_store(); - set_fuel(&mut store, 1_000); + store.set_fuel(1_000).unwrap(); let wasm = ops.to_wasm_binary(); log_wasm(&wasm); @@ -837,20 +837,6 @@ impl Drop for SignalOnDrop { } } -/// Set the amount of fuel in a store to a given value -pub fn set_fuel(store: &mut Store, fuel: u64) { - // Determine the amount of fuel already within the store, if any, and - // add/consume as appropriate to set the remaining amount to` fuel`. - let remaining = store.consume_fuel(0).unwrap(); - if fuel > remaining { - store.add_fuel(fuel - remaining).unwrap(); - } else { - store.consume_fuel(remaining - fuel).unwrap(); - } - // double-check that the store has the expected amount of fuel remaining - assert_eq!(store.consume_fuel(0).unwrap(), fuel); -} - /// Generate and execute a `crate::generators::component_types::TestCase` using the specified `input` to create /// arbitrary types and values. pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()> { diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 3b9aa6cad1cd..ee065e421e01 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -338,7 +338,7 @@ impl Config { /// /// * Alternatively you can enable the /// [`Config::consume_fuel`](crate::Config::consume_fuel) method as well - /// as [`crate::Store::out_of_fuel_async_yield`] When doing so this will + /// as [`crate::Store::fuel_async_yield_interval`] When doing so this will /// configure Wasmtime futures to yield periodically while they're /// executing WebAssembly code. After consuming the specified amount of /// fuel wasm futures will return `Poll::Pending` from their `poll` @@ -456,8 +456,9 @@ impl Config { /// /// This can be used to deterministically prevent infinitely-executing /// WebAssembly code by instrumenting generated code to consume fuel as it - /// executes. When fuel runs out the behavior is defined by configuration - /// within a [`Store`], and by default a trap is raised. + /// executes. When fuel runs out a trap is raised, however [`Store`] can be + /// configured to yield execution periodically via + /// [`crate::Store::fuel_async_yield_interval`]. /// /// Note that a [`Store`] starts with no fuel, so if you enable this option /// you'll have to be sure to pour some fuel into [`Store`] before diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index a551b64daef6..3a23d51d4da7 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -1895,50 +1895,26 @@ impl Caller<'_, T> { self.store.gc() } - /// Returns the fuel consumed by this store. - /// - /// For more information see [`Store::fuel_consumed`](crate::Store::fuel_consumed) - pub fn fuel_consumed(&self) -> Option { - self.store.fuel_consumed() - } - /// Returns the remaining fuel in the store. /// - /// For more information see [`Store::fuel_remaining`](crate::Store::fuel_remaining) - pub fn fuel_remaining(&self) -> Option { - self.store.fuel_remaining() - } - - /// Inject more fuel into this store to be consumed when executing wasm code. - /// - /// For more information see [`Store::add_fuel`](crate::Store::add_fuel) - pub fn add_fuel(&mut self, fuel: u64) -> Result<()> { - self.store.add_fuel(fuel) + /// For more information see [`Store::get_fuel`](crate::Store::get_fuel) + pub fn get_fuel(&self) -> Result { + self.store.get_fuel() } - /// Synthetically consumes fuel from the store. + /// Set the amount of fuel in this store to be consumed when executing wasm code. /// - /// For more information see [`Store::consume_fuel`](crate::Store::consume_fuel) - pub fn consume_fuel(&mut self, fuel: u64) -> Result { - self.store.consume_fuel(fuel) - } - - /// Configures this `Store` to trap whenever fuel runs out. - /// - /// For more information see - /// [`Store::out_of_fuel_trap`](crate::Store::out_of_fuel_trap) - pub fn out_of_fuel_trap(&mut self) { - self.store.out_of_fuel_trap() + /// For more information see [`Store::set_fuel`](crate::Store::set_fuel) + pub fn set_fuel(&mut self, fuel: u64) -> Result<()> { + self.store.set_fuel(fuel) } - /// Configures this `Store` to yield while executing futures whenever fuel - /// runs out. + /// Configures this `Store` to yield while executing futures every N units of fuel. /// /// For more information see - /// [`Store::out_of_fuel_async_yield`](crate::Store::out_of_fuel_async_yield) - pub fn out_of_fuel_async_yield(&mut self, injection_count: u64, fuel_to_inject: u64) { - self.store - .out_of_fuel_async_yield(injection_count, fuel_to_inject) + /// [`Store::fuel_async_yield_interval`](crate::Store::fuel_async_yield_interval) + pub fn fuel_async_yield_interval(&mut self, interval: Option) -> Result<()> { + self.store.fuel_async_yield_interval(interval) } } diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index e4745d3adefa..edc6d2107644 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -84,11 +84,11 @@ use crate::{module::ModuleRegistry, Engine, Module, Trap, Val, ValRaw}; use crate::{Global, Instance, Memory}; use anyhow::{anyhow, bail, Result}; use std::cell::UnsafeCell; -use std::convert::TryFrom; use std::fmt; use std::future::Future; use std::marker; use std::mem::{self, ManuallyDrop}; +use std::num::NonZeroU64; use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::ptr; @@ -306,12 +306,14 @@ pub struct StoreOpaque { memory_limit: usize, table_count: usize, table_limit: usize, - /// An adjustment to add to the fuel consumed value in `runtime_limits` above - /// to get the true amount of fuel consumed. - fuel_adj: i64, #[cfg(feature = "async")] async_state: AsyncState, - out_of_gas_behavior: OutOfGas, + // If fuel_yield_interval is enabled, then we store the remaining fuel (that isn't in + // runtime_limits) here. The total amount of fuel is the runtime limits and reserve added + // together. Then when we run out of gas, we inject the yield amount from the reserve + // until the reserve is empty. + fuel_reserve: u64, + fuel_yield_interval: Option, /// Indexed data within this `Store`, used to store information about /// globals, functions, memories, etc. /// @@ -458,15 +460,6 @@ enum StoreInstanceKind { Dummy, } -#[derive(Copy, Clone)] -enum OutOfGas { - Trap, - InjectFuel { - injection_count: u64, - fuel_to_inject: u64, - }, -} - impl Store { /// Creates a new [`Store`] to be associated with the given [`Engine`] and /// `data` provided. @@ -498,13 +491,13 @@ impl Store { memory_limit: crate::DEFAULT_MEMORY_LIMIT, table_count: 0, table_limit: crate::DEFAULT_TABLE_LIMIT, - fuel_adj: 0, #[cfg(feature = "async")] async_state: AsyncState { current_suspend: UnsafeCell::new(ptr::null()), current_poll_cx: UnsafeCell::new(ptr::null_mut()), }, - out_of_gas_behavior: OutOfGas::Trap, + fuel_reserve: 0, + fuel_yield_interval: None, store_data: ManuallyDrop::new(StoreData::new()), default_caller: InstanceHandle::null(), hostcall_val_storage: Vec::new(), @@ -787,31 +780,17 @@ impl Store { self.inner.gc() } - /// Returns the amount of fuel consumed by this store's execution so far. - /// - /// If fuel consumption is not enabled via - /// [`Config::consume_fuel`](crate::Config::consume_fuel) then this - /// function will return `None`. Also note that fuel, if enabled, must be - /// originally configured via [`Store::add_fuel`] or [`Store::reset_fuel`]. - /// - /// Note that this function returns the amount of fuel consumed since the - /// last time [`reset_fuel`][Store::reset_fuel] was called, or since the creation - /// of the store if it's never been called. - pub fn fuel_consumed(&self) -> Option { - self.inner.fuel_consumed() - } - - /// Returns remaining fuel in this [`Store`]. + /// Returns the amount fuel in this [`Store`]. /// /// If fuel consumption is not enabled via /// [`Config::consume_fuel`](crate::Config::consume_fuel) then this /// function will return `None`. Also note that fuel, if enabled, must be - /// originally configured via [`Store::add_fuel`]. - pub fn fuel_remaining(&mut self) -> Option { - self.inner.fuel_remaining() + /// originally configured via [`Store::set_fuel`]. + pub fn get_fuel(&self) -> Result { + self.inner.get_fuel() } - /// Adds fuel to this [`Store`] for wasm to consume while executing. + /// Set the fuel to this [`Store`] for wasm to consume while executing. /// /// For this method to work fuel consumption must be enabled via /// [`Config::consume_fuel`](crate::Config::consume_fuel). By default a @@ -824,66 +803,14 @@ impl Store { /// units, as any execution cost associated with them involves other /// instructions which do consume fuel. /// - /// Note that at this time when fuel is entirely consumed it will cause - /// wasm to trap. More usages of fuel are planned for the future. + /// Note that when fuel is entirely consumed it will cause wasm to trap. /// /// # Errors /// /// This function will return an error if fuel consumption is not enabled via /// [`Config::consume_fuel`](crate::Config::consume_fuel). - pub fn add_fuel(&mut self, fuel: u64) -> Result<()> { - self.inner.add_fuel(fuel) - } - /// - /// Set the remaining fuel to this [`Store`] for wasm to consume while executing. - /// - /// See [`Store::add_fuel`] for more information about fuel. - /// - /// This method will also reset the amount of fuel that has been consumed. - /// - /// # Errors - /// - /// This function will return an error if fuel consumption is not enabled via - /// [`Config::consume_fuel`](crate::Config::consume_fuel). - pub fn reset_fuel(&mut self, fuel: u64) -> Result<()> { - self.inner.reset_fuel(fuel) - } - - /// Synthetically consumes fuel from this [`Store`]. - /// - /// For this method to work fuel consumption must be enabled via - /// [`Config::consume_fuel`](crate::Config::consume_fuel). - /// - /// WebAssembly execution will automatically consume fuel but if so desired - /// the embedder can also consume fuel manually to account for relative - /// costs of host functions, for example. - /// - /// This function will attempt to consume `fuel` units of fuel from within - /// this store. If the remaining amount of fuel allows this then `Ok(N)` is - /// returned where `N` is the amount of remaining fuel. Otherwise an error - /// is returned and no fuel is consumed. - /// - /// # Errors - /// - /// This function will return an error either if fuel consumption is not - /// enabled via [`Config::consume_fuel`](crate::Config::consume_fuel) or if - /// `fuel` exceeds the amount of remaining fuel within this store. - pub fn consume_fuel(&mut self, fuel: u64) -> Result { - self.inner.consume_fuel(fuel) - } - - /// Configures a [`Store`] to generate a [`Trap`] whenever it runs out of - /// fuel. - /// - /// When a [`Store`] is configured to consume fuel with - /// [`Config::consume_fuel`](crate::Config::consume_fuel) this method will - /// configure what happens when fuel runs out. Specifically a WebAssembly - /// trap will be raised and the current execution of WebAssembly will be - /// aborted. - /// - /// This is the default behavior for running out of fuel. - pub fn out_of_fuel_trap(&mut self) { - self.inner.out_of_fuel_trap() + pub fn set_fuel(&mut self, fuel: u64) -> Result<()> { + self.inner.set_fuel(fuel) } /// Configures a [`Store`] to yield execution of async WebAssembly code @@ -891,11 +818,10 @@ impl Store { /// /// When a [`Store`] is configured to consume fuel with /// [`Config::consume_fuel`](crate::Config::consume_fuel) this method will - /// configure what happens when fuel runs out. Specifically executing - /// WebAssembly will be suspended and control will be yielded back to the - /// caller. This is only suitable with use of a store associated with an [async - /// config](crate::Config::async_support) because only then are futures used and yields - /// are possible. + /// configure WebAssembly to be suspended and control will be yielded back to the + /// caller every `interval` units of fuel consumed. This is only suitable with use of + /// a store associated with an [async config](crate::Config::async_support) because + /// only then are futures used and yields are possible. /// /// The purpose of this behavior is to ensure that futures which represent /// execution of WebAssembly do not execute too long inside their @@ -908,21 +834,15 @@ impl Store { /// WebAssembly will continue to execute, just after giving the host an /// opportunity to do something else. /// - /// The `fuel_to_inject` parameter indicates how much fuel should be - /// automatically re-injected after fuel runs out. This is how much fuel - /// will be consumed between yields of an async future. + /// The `interval` parameter indicates how much fuel should be + /// consumed between yields of an async future. When fuel runs out wasm will trap. /// - /// The `injection_count` parameter indicates how many times this fuel will - /// be injected. Multiplying the two parameters is the total amount of fuel - /// this store is allowed before wasm traps. + /// # Error /// - /// # Panics - /// - /// This method will panic if it is not called on a store associated with an [async + /// This method will error if it is not called on a store associated with an [async /// config](crate::Config::async_support). - pub fn out_of_fuel_async_yield(&mut self, injection_count: u64, fuel_to_inject: u64) { - self.inner - .out_of_fuel_async_yield(injection_count, fuel_to_inject) + pub fn fuel_async_yield_interval(&mut self, interval: Option) -> Result<()> { + self.inner.fuel_async_yield_interval(interval) } /// Sets the epoch deadline to a certain number of ticks in the future. @@ -1066,18 +986,11 @@ impl<'a, T> StoreContext<'a, T> { self.0.data() } - /// Returns the fuel consumed by this store. - /// - /// For more information see [`Store::fuel_consumed`]. - pub fn fuel_consumed(&self) -> Option { - self.0.fuel_consumed() - } - - /// Returns remaining fuel in this store. + /// Returns the remaining fuel in this store. /// - /// For more information see [`Store::fuel_remaining`] - pub fn fuel_remaining(&mut self) -> Option { - self.0.fuel_remaining() + /// For more information see [`Store::get_fuel`]. + pub fn get_fuel(&self) -> Result { + self.0.get_fuel() } } @@ -1108,55 +1021,25 @@ impl<'a, T> StoreContextMut<'a, T> { self.0.gc() } - /// Returns the fuel consumed by this store. - /// - /// For more information see [`Store::fuel_consumed`]. - pub fn fuel_consumed(&self) -> Option { - self.0.fuel_consumed() - } - /// Returns remaining fuel in this store. /// - /// For more information see [`Store::fuel_remaining`] - pub fn fuel_remaining(&self) -> Option { - self.0.fuel_remaining() - } - - /// Inject more fuel into this store to be consumed when executing wasm code. - /// - /// For more information see [`Store::add_fuel`] - pub fn add_fuel(&mut self, fuel: u64) -> Result<()> { - self.0.add_fuel(fuel) - } - - /// Synthetically consume fuel from this store. - /// - /// For more information see [`Store::consume_fuel`] - pub fn consume_fuel(&mut self, fuel: u64) -> Result { - self.0.consume_fuel(fuel) - } - - /// Set the fuel for this store to be consumed when executing wasm code. - /// - /// For more information see [`Store::reset_fuel`] - pub fn reset_fuel(&mut self, fuel: u64) -> Result<()> { - self.0.reset_fuel(fuel) + /// For more information see [`Store::get_fuel`] + pub fn get_fuel(&self) -> Result { + self.0.get_fuel() } - /// Configures this `Store` to trap whenever fuel runs out. + /// Set the amount of fuel in this store. /// - /// For more information see [`Store::out_of_fuel_trap`] - pub fn out_of_fuel_trap(&mut self) { - self.0.out_of_fuel_trap() + /// For more information see [`Store::set_fuel`] + pub fn set_fuel(&mut self, fuel: u64) -> Result<()> { + self.0.set_fuel(fuel) } - /// Configures this `Store` to yield while executing futures whenever fuel - /// runs out. + /// Configures this `Store` to periodically yield while executing futures. /// - /// For more information see [`Store::out_of_fuel_async_yield`] - pub fn out_of_fuel_async_yield(&mut self, injection_count: u64, fuel_to_inject: u64) { - self.0 - .out_of_fuel_async_yield(injection_count, fuel_to_inject) + /// For more information see [`Store::fuel_async_yield_interval`] + pub fn fuel_async_yield_interval(&mut self, interval: Option) -> Result<()> { + self.0.fuel_async_yield_interval(interval) } /// Sets the epoch deadline to a certain number of ticks in the future. @@ -1224,6 +1107,45 @@ impl StoreInner { } } +fn get_fuel(injected_fuel: i64, fuel_reserve: u64) -> u64 { + fuel_reserve.saturating_add_signed(-injected_fuel) +} + +// Add remaining fuel from the reserve into the active fuel if there is any left. +fn refuel( + injected_fuel: &mut i64, + fuel_reserve: &mut u64, + yield_interval: Option, +) -> bool { + let fuel = get_fuel(*injected_fuel, *fuel_reserve); + if fuel > 0 { + set_fuel(injected_fuel, fuel_reserve, yield_interval, fuel); + true + } else { + false + } +} + +fn set_fuel( + injected_fuel: &mut i64, + fuel_reserve: &mut u64, + yield_interval: Option, + new_fuel_amount: u64, +) { + let interval = yield_interval.unwrap_or(NonZeroU64::MAX).get(); + // If we're yielding periodically we only store the "active" amount of fuel into consumed_ptr + // for the VM to use. + let injected = std::cmp::min(interval, new_fuel_amount); + // Fuel in the VM is stored as an i64, so we have to cap the amount of fuel we inject into the + // VM at once to be i64 range. + let injected = std::cmp::min(injected, i64::MAX as u64); + // Add whatever is left over after injection to the reserve for later use. + *fuel_reserve = new_fuel_amount - injected; + // Within the VM we increment to count fuel, so inject a negative amount. The VM will halt when + // this counter is positive. + *injected_fuel = -(injected as i64); +} + #[doc(hidden)] impl StoreOpaque { pub fn id(&self) -> StoreId { @@ -1465,35 +1387,55 @@ impl StoreOpaque { }) } - pub fn fuel_consumed(&self) -> Option { - if !self.engine.config().tunables.consume_fuel { - return None; - } - let consumed = unsafe { *self.runtime_limits.fuel_consumed.get() }; - Some(u64::try_from(self.fuel_adj + consumed).unwrap()) + pub fn get_fuel(&self) -> Result { + anyhow::ensure!( + self.engine().config().tunables.consume_fuel, + "fuel is not configured in this store" + ); + let injected_fuel = unsafe { *self.runtime_limits.fuel_consumed.get() }; + Ok(get_fuel(injected_fuel, self.fuel_reserve)) } - fn fuel_remaining(&self) -> Option { - if !self.engine.config().tunables.consume_fuel { - return None; - } - let consumed = unsafe { *self.runtime_limits.fuel_consumed.get() }; - Some(u64::try_from(-consumed).unwrap()) + fn refuel(&mut self) -> bool { + let injected_fuel = unsafe { &mut *self.runtime_limits.fuel_consumed.get() }; + refuel( + injected_fuel, + &mut self.fuel_reserve, + self.fuel_yield_interval, + ) } - fn out_of_fuel_trap(&mut self) { - self.out_of_gas_behavior = OutOfGas::Trap; + pub fn set_fuel(&mut self, fuel: u64) -> Result<()> { + anyhow::ensure!( + self.engine().config().tunables.consume_fuel, + "fuel is not configured in this store" + ); + let injected_fuel = unsafe { &mut *self.runtime_limits.fuel_consumed.get() }; + set_fuel( + injected_fuel, + &mut self.fuel_reserve, + self.fuel_yield_interval, + fuel, + ); + Ok(()) } - fn out_of_fuel_async_yield(&mut self, injection_count: u64, fuel_to_inject: u64) { - assert!( - self.async_support(), - "cannot use `out_of_fuel_async_yield` without enabling async support in the config" + pub fn fuel_async_yield_interval(&mut self, interval: Option) -> Result<()> { + anyhow::ensure!( + self.engine().config().tunables.consume_fuel, + "fuel is not configured in this store" ); - self.out_of_gas_behavior = OutOfGas::InjectFuel { - injection_count, - fuel_to_inject, - }; + anyhow::ensure!( + self.engine().config().async_support, + "async support is not configured in this store" + ); + anyhow::ensure!( + interval != Some(0), + "fuel_async_yield_interval must not be 0" + ); + self.fuel_yield_interval = interval.and_then(|i| NonZeroU64::new(i)); + // Reset the fuel active + reserve states by resetting the amount. + self.set_fuel(self.get_fuel()?) } /// Yields execution to the caller on out-of-gas or epoch interruption. @@ -1541,69 +1483,6 @@ impl StoreOpaque { } } - fn add_fuel(&mut self, fuel: u64) -> Result<()> { - anyhow::ensure!( - self.engine().config().tunables.consume_fuel, - "fuel is not configured in this store" - ); - - // Fuel is stored as an i64, so we need to cast it. If the provided fuel - // value overflows that just assume that i64::max will suffice. Wasm - // execution isn't fast enough to burn through i64::max fuel in any - // reasonable amount of time anyway. - let fuel = i64::try_from(fuel).unwrap_or(i64::max_value()); - let adj = self.fuel_adj; - let consumed_ptr = unsafe { &mut *self.runtime_limits.fuel_consumed.get() }; - - match (consumed_ptr.checked_sub(fuel), adj.checked_add(fuel)) { - // If we succesfully did arithmetic without overflowing then we can - // just update our fields. - (Some(consumed), Some(adj)) => { - self.fuel_adj = adj; - *consumed_ptr = consumed; - } - - // Otherwise something overflowed. Make sure that we preserve the - // amount of fuel that's already consumed, but otherwise assume that - // we were given infinite fuel. - _ => { - self.fuel_adj = i64::max_value(); - *consumed_ptr = (*consumed_ptr + adj) - i64::max_value(); - } - } - - Ok(()) - } - - fn consume_fuel(&mut self, fuel: u64) -> Result { - anyhow::ensure!( - self.engine().config().tunables.consume_fuel, - "fuel is not configured in this store" - ); - let consumed_ptr = unsafe { &mut *self.runtime_limits.fuel_consumed.get() }; - match i64::try_from(fuel) - .ok() - .and_then(|fuel| consumed_ptr.checked_add(fuel)) - { - Some(consumed) if consumed <= 0 => { - *consumed_ptr = consumed; - Ok(u64::try_from(-consumed).unwrap()) - } - _ => bail!("not enough fuel remaining in store"), - } - } - - fn reset_fuel(&mut self, fuel: u64) -> Result<()> { - anyhow::ensure!( - self.engine().config().tunables.consume_fuel, - "fuel is not configured in this store" - ); - let fuel = i64::try_from(fuel).unwrap_or(i64::max_value()); - self.fuel_adj = fuel; - unsafe { *self.runtime_limits.fuel_consumed.get() = -fuel }; - Ok(()) - } - #[inline] pub fn signal_handler(&self) -> Option<*const SignalHandler<'static>> { let handler = self.signal_handler.as_ref()?; @@ -2212,28 +2091,15 @@ unsafe impl wasmtime_runtime::Store for StoreInner { } } - fn out_of_gas(&mut self) -> Result<(), anyhow::Error> { - return match &mut self.out_of_gas_behavior { - OutOfGas::Trap => Err(Trap::OutOfFuel.into()), - #[cfg(feature = "async")] - OutOfGas::InjectFuel { - injection_count, - fuel_to_inject, - } => { - if *injection_count == 0 { - return Err(Trap::OutOfFuel.into()); - } - *injection_count -= 1; - let fuel = *fuel_to_inject; - self.async_yield_impl()?; - if fuel > 0 { - self.add_fuel(fuel).unwrap(); - } - Ok(()) - } - #[cfg(not(feature = "async"))] - OutOfGas::InjectFuel { .. } => unreachable!(), - }; + fn out_of_gas(&mut self) -> Result<()> { + if !self.refuel() { + return Err(Trap::OutOfFuel.into()); + } + #[cfg(feature = "async")] + if self.fuel_yield_interval.is_some() { + self.async_yield_impl()?; + } + Ok(()) } fn new_epoch(&mut self) -> Result { @@ -2398,3 +2264,125 @@ impl Drop for Reset { } } } + +#[cfg(test)] +mod tests { + use super::{get_fuel, refuel, set_fuel}; + use std::num::NonZeroU64; + + struct FuelTank { + pub consumed_fuel: i64, + pub reserve_fuel: u64, + pub yield_interval: Option, + } + + impl FuelTank { + fn new() -> Self { + FuelTank { + consumed_fuel: 0, + reserve_fuel: 0, + yield_interval: None, + } + } + fn get_fuel(&self) -> u64 { + get_fuel(self.consumed_fuel, self.reserve_fuel) + } + fn refuel(&mut self) -> bool { + refuel( + &mut self.consumed_fuel, + &mut self.reserve_fuel, + self.yield_interval, + ) + } + fn set_fuel(&mut self, fuel: u64) { + set_fuel( + &mut self.consumed_fuel, + &mut self.reserve_fuel, + self.yield_interval, + fuel, + ); + } + } + + #[test] + fn smoke() { + let mut tank = FuelTank::new(); + tank.set_fuel(10); + assert_eq!(tank.consumed_fuel, -10); + assert_eq!(tank.reserve_fuel, 0); + + tank.yield_interval = NonZeroU64::new(10); + tank.set_fuel(25); + assert_eq!(tank.consumed_fuel, -10); + assert_eq!(tank.reserve_fuel, 15); + } + + #[test] + fn does_not_lose_precision() { + let mut tank = FuelTank::new(); + tank.set_fuel(u64::MAX); + assert_eq!(tank.get_fuel(), u64::MAX); + + tank.set_fuel(i64::MAX as u64); + assert_eq!(tank.get_fuel(), i64::MAX as u64); + + tank.set_fuel(i64::MAX as u64 + 1); + assert_eq!(tank.get_fuel(), i64::MAX as u64 + 1); + } + + #[test] + fn yielding_does_not_lose_precision() { + let mut tank = FuelTank::new(); + + tank.yield_interval = NonZeroU64::new(10); + tank.set_fuel(u64::MAX); + assert_eq!(tank.get_fuel(), u64::MAX); + assert_eq!(tank.consumed_fuel, -10); + assert_eq!(tank.reserve_fuel, u64::MAX - 10); + + tank.yield_interval = NonZeroU64::new(u64::MAX); + tank.set_fuel(u64::MAX); + assert_eq!(tank.get_fuel(), u64::MAX); + assert_eq!(tank.consumed_fuel, -i64::MAX); + assert_eq!(tank.reserve_fuel, u64::MAX - (i64::MAX as u64)); + + tank.yield_interval = NonZeroU64::new((i64::MAX as u64) + 1); + tank.set_fuel(u64::MAX); + assert_eq!(tank.get_fuel(), u64::MAX); + assert_eq!(tank.consumed_fuel, -i64::MAX); + assert_eq!(tank.reserve_fuel, u64::MAX - (i64::MAX as u64)); + } + + #[test] + fn refueling() { + // It's possible to fuel to have consumed over the limit as some instructions can consume + // multiple units of fuel at once. Refueling should be strict in it's consumption and not + // add more fuel than there is. + let mut tank = FuelTank::new(); + + tank.yield_interval = NonZeroU64::new(10); + tank.reserve_fuel = 42; + tank.consumed_fuel = 4; + assert!(tank.refuel()); + assert_eq!(tank.reserve_fuel, 28); + assert_eq!(tank.consumed_fuel, -10); + + tank.yield_interval = NonZeroU64::new(1); + tank.reserve_fuel = 8; + tank.consumed_fuel = 4; + assert_eq!(tank.get_fuel(), 4); + assert!(tank.refuel()); + assert_eq!(tank.reserve_fuel, 3); + assert_eq!(tank.consumed_fuel, -1); + assert_eq!(tank.get_fuel(), 4); + + tank.yield_interval = NonZeroU64::new(10); + tank.reserve_fuel = 3; + tank.consumed_fuel = 4; + assert_eq!(tank.get_fuel(), 0); + assert!(!tank.refuel()); + assert_eq!(tank.reserve_fuel, 3); + assert_eq!(tank.consumed_fuel, 4); + assert_eq!(tank.get_fuel(), 0); + } +} diff --git a/examples/async.cpp b/examples/async.cpp index 2656e73d1d91..a5feae1152da 100644 --- a/examples/async.cpp +++ b/examples/async.cpp @@ -181,9 +181,8 @@ int main() { // This pointer is unowned. auto *context = wasmtime_store_context(store.get()); // Configure the store to periodically yield control - wasmtime_context_out_of_fuel_async_yield(context, - /*injection_count=*/10, - /*fuel_to_inject=*/10000); + wasmtime_context_set_fuel(context, 100000); + wasmtime_context_fuel_async_yield_interval(context, /*interval=*/10000); auto compiled_module = compile_wat_module_from_file(engine.get(), "examples/async.wat"); diff --git a/examples/fuel.c b/examples/fuel.c index 9b0eaa6b09ac..173bdcf58d77 100644 --- a/examples/fuel.c +++ b/examples/fuel.c @@ -43,9 +43,9 @@ int main() { assert(store != NULL); wasmtime_context_t *context = wasmtime_store_context(store); - error = wasmtime_context_add_fuel(context, 10000); + error = wasmtime_context_set_fuel(context, 10000); if (error != NULL) - exit_with_error("failed to add fuel", error, NULL); + exit_with_error("failed to set fuel", error, NULL); // Load our input file to parse it next FILE* file = fopen("examples/fuel.wat", "r"); @@ -93,7 +93,7 @@ int main() { // Call it repeatedly until it fails for (int n = 1; ; n++) { uint64_t fuel_before; - wasmtime_context_fuel_consumed(context, &fuel_before); + wasmtime_context_get_fuel(context, &fuel_before); wasmtime_val_t params[1]; params[0].kind = WASMTIME_I32; params[0].of.i32 = n; @@ -110,13 +110,13 @@ int main() { } uint64_t fuel_after; - wasmtime_context_fuel_consumed(context, &fuel_after); + wasmtime_context_get_fuel(context, &fuel_after); assert(results[0].kind == WASMTIME_I32); - printf("fib(%d) = %d [consumed %lld fuel]\n", n, results[0].of.i32, fuel_after - fuel_before); + printf("fib(%d) = %d [consumed %lu fuel]\n", n, results[0].of.i32, fuel_after - fuel_before); - error = wasmtime_context_add_fuel(context, fuel_after - fuel_before); + error = wasmtime_context_set_fuel(context, 10000); if (error != NULL) - exit_with_error("failed to add fuel", error, NULL); + exit_with_error("failed to set fuel", error, NULL); } // Clean up after ourselves at this point diff --git a/examples/fuel.rs b/examples/fuel.rs index e6f4c74ecb37..179c68d1ce83 100644 --- a/examples/fuel.rs +++ b/examples/fuel.rs @@ -10,14 +10,14 @@ fn main() -> Result<()> { config.consume_fuel(true); let engine = Engine::new(&config)?; let mut store = Store::new(&engine, ()); - store.add_fuel(10_000)?; + store.set_fuel(10_000)?; let module = Module::from_file(store.engine(), "examples/fuel.wat")?; let instance = Instance::new(&mut store, &module, &[])?; // Invoke `fibonacci` export with higher and higher numbers until we exhaust our fuel. let fibonacci = instance.get_typed_func::(&mut store, "fibonacci")?; for n in 1.. { - let fuel_before = store.fuel_consumed().unwrap(); + let fuel_before = store.get_fuel().unwrap(); let output = match fibonacci.call(&mut store, n) { Ok(v) => v, Err(e) => { @@ -26,9 +26,9 @@ fn main() -> Result<()> { break; } }; - let fuel_consumed = store.fuel_consumed().unwrap() - fuel_before; + let fuel_consumed = fuel_before - store.get_fuel().unwrap(); println!("fib({}) = {} [consumed {} fuel]", n, output, fuel_consumed); - store.add_fuel(fuel_consumed)?; + store.set_fuel(10_000)?; } Ok(()) } diff --git a/examples/tokio/main.rs b/examples/tokio/main.rs index e90f6de9727c..2073f38886fa 100644 --- a/examples/tokio/main.rs +++ b/examples/tokio/main.rs @@ -94,9 +94,11 @@ async fn run_wasm(inputs: Inputs) -> Result<(), Error> { .build(); let mut store = Store::new(&inputs.env.engine, wasi); + // Put effectively unlimited fuel so it can run forever. + store.set_fuel(u64::MAX)?; // WebAssembly execution will be paused for an async yield every time it - // consumes 10000 fuel. Fuel will be refilled u64::MAX times. - store.out_of_fuel_async_yield(u64::MAX, 10000); + // consumes 10000 fuel. + store.fuel_async_yield_interval(Some(10000))?; // Instantiate into our own unique store using the shared linker, afterwards // acquiring the `_start` function for the module and executing it. diff --git a/src/commands/run.rs b/src/commands/run.rs index 77af3882791d..2e80c9708560 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -176,7 +176,7 @@ impl RunCommand { // If fuel has been configured, we want to add the configured // fuel amount to this store. if let Some(fuel) = self.run.common.wasm.fuel { - store.add_fuel(fuel)?; + store.set_fuel(fuel)?; } // Load the preload wasm modules. diff --git a/src/commands/serve.rs b/src/commands/serve.rs index 6993217839a7..7b11b12edad2 100644 --- a/src/commands/serve.rs +++ b/src/commands/serve.rs @@ -179,7 +179,7 @@ impl ServeCommand { // If fuel has been configured, we want to add the configured // fuel amount to this store. if let Some(fuel) = self.run.common.wasm.fuel { - store.add_fuel(fuel)?; + store.set_fuel(fuel)?; } Ok(store) diff --git a/tests/all/async_functions.rs b/tests/all/async_functions.rs index f2cac9ffdd77..3796c9b05585 100644 --- a/tests/all/async_functions.rs +++ b/tests/all/async_functions.rs @@ -311,7 +311,8 @@ async fn cancel_during_run() { async fn iloop_with_fuel() { let engine = Engine::new(Config::new().async_support(true).consume_fuel(true)).unwrap(); let mut store = Store::new(&engine, ()); - store.out_of_fuel_async_yield(1_000, 10); + store.set_fuel(10_000).unwrap(); + store.fuel_async_yield_interval(Some(100)).unwrap(); let module = Module::new( &engine, " @@ -326,14 +327,15 @@ async fn iloop_with_fuel() { // This should yield a bunch of times but eventually finish let (_, pending) = CountPending::new(Box::pin(instance)).await; - assert!(pending > 100); + assert_eq!(pending, 99); } #[tokio::test] async fn fuel_eventually_finishes() { let engine = Engine::new(Config::new().async_support(true).consume_fuel(true)).unwrap(); let mut store = Store::new(&engine, ()); - store.out_of_fuel_async_yield(u64::max_value(), 10); + store.set_fuel(u64::MAX).unwrap(); + store.fuel_async_yield_interval(Some(10)).unwrap(); let module = Module::new( &engine, " diff --git a/tests/all/component_model/async.rs b/tests/all/component_model/async.rs index 116ced19cc14..cbb398fdd3f8 100644 --- a/tests/all/component_model/async.rs +++ b/tests/all/component_model/async.rs @@ -155,7 +155,8 @@ async fn resume_separate_thread() -> Result<()> { execute_across_threads(async move { let mut store = Store::new(&engine, ()); - store.out_of_fuel_async_yield(u64::MAX, 1); + store.set_fuel(u64::MAX).unwrap(); + store.fuel_async_yield_interval(Some(1)).unwrap(); linker.instantiate_async(&mut store, &component).await?; Ok::<_, anyhow::Error>(()) }) @@ -207,7 +208,8 @@ async fn poll_through_wasm_activation() -> Result<()> { let engine = engine.clone(); async move { let mut store = Store::new(&engine, ()); - store.out_of_fuel_async_yield(u64::MAX, 1); + store.set_fuel(u64::MAX).unwrap(); + store.fuel_async_yield_interval(Some(1)).unwrap(); let instance = linker.instantiate_async(&mut store, &component).await?; let func = instance.get_typed_func::<(Vec,), ()>(&mut store, "run")?; func.call_async(&mut store, (vec![1, 2, 3],)).await?; diff --git a/tests/all/fuel.rs b/tests/all/fuel.rs index df046763c95e..6686daf5cbc0 100644 --- a/tests/all/fuel.rs +++ b/tests/all/fuel.rs @@ -53,9 +53,9 @@ fn fuel_consumed(wasm: &[u8]) -> u64 { let engine = Engine::new(&config).unwrap(); let module = Module::new(&engine, wasm).unwrap(); let mut store = Store::new(&engine, ()); - store.add_fuel(u64::max_value()).unwrap(); + store.set_fuel(u64::MAX).unwrap(); drop(Instance::new(&mut store, &module, &[])); - store.fuel_consumed().unwrap() + u64::MAX - store.get_fuel().unwrap() } #[test] @@ -116,7 +116,7 @@ fn iloop() { let engine = Engine::new(&config).unwrap(); let module = Module::new(&engine, wat).unwrap(); let mut store = Store::new(&engine, ()); - store.add_fuel(10_000).unwrap(); + store.set_fuel(10_000).unwrap(); let error = Instance::new(&mut store, &module, &[]).err().unwrap(); assert_eq!(error.downcast::().unwrap(), Trap::OutOfFuel); } @@ -128,29 +128,10 @@ fn manual_fuel() { config.consume_fuel(true); let engine = Engine::new(&config).unwrap(); let mut store = Store::new(&engine, ()); - store.add_fuel(10_000).unwrap(); - assert_eq!(store.fuel_consumed(), Some(0)); - assert_eq!(store.fuel_remaining(), Some(10_000)); - assert_eq!(store.consume_fuel(1).unwrap(), 9_999); - assert_eq!(store.fuel_consumed(), Some(1)); - assert_eq!(store.fuel_remaining(), Some(9_999)); - assert!(store.consume_fuel(10_000).is_err()); - assert_eq!(store.consume_fuel(999).unwrap(), 9_000); - assert!(store.consume_fuel(10_000).is_err()); - assert_eq!(store.consume_fuel(8998).unwrap(), 2); - assert!(store.consume_fuel(3).is_err()); - assert_eq!(store.consume_fuel(1).unwrap(), 1); - assert_eq!(store.consume_fuel(1).unwrap(), 0); - assert_eq!(store.consume_fuel(0).unwrap(), 0); - assert_eq!(store.fuel_remaining(), Some(0)); - store.add_fuel(5_000).unwrap(); - assert_eq!(store.fuel_consumed(), Some(10_000)); - assert_eq!(store.consume_fuel(2_500).unwrap(), 2_500); - assert_eq!(store.fuel_consumed(), Some(12_500)); - store.reset_fuel(5_000).unwrap(); - assert_eq!(store.fuel_consumed(), Some(0)); - assert_eq!(store.consume_fuel(2_500).unwrap(), 2_500); - assert_eq!(store.fuel_consumed(), Some(2_500)); + store.set_fuel(10_000).unwrap(); + assert_eq!(store.get_fuel().ok(), Some(10_000)); + assert_eq!(store.set_fuel(1).ok(), Some(())); + assert_eq!(store.get_fuel().ok(), Some(1)); } #[test] @@ -173,12 +154,11 @@ fn host_function_consumes_all() { ) .unwrap(); let mut store = Store::new(&engine, ()); - store.add_fuel(FUEL).unwrap(); + store.set_fuel(FUEL).unwrap(); let func = Func::wrap(&mut store, |mut caller: Caller<'_, ()>| { - let consumed = caller.fuel_consumed().unwrap(); - let remaining = caller.fuel_remaining().unwrap(); - assert_eq!(remaining, FUEL - consumed); - assert_eq!(caller.consume_fuel(remaining - 1).unwrap(), 1); + let remaining = caller.get_fuel().unwrap(); + assert_eq!(remaining, FUEL - 2); + assert!(caller.set_fuel(1).is_ok()); }); let instance = Instance::new(&mut store, &module, &[func.into()]).unwrap(); @@ -193,11 +173,8 @@ fn manual_edge_cases() { config.consume_fuel(true); let engine = Engine::new(&config).unwrap(); let mut store = Store::new(&engine, ()); - store.add_fuel(u64::MAX).unwrap(); - assert_eq!(store.fuel_consumed(), Some(0)); - assert!(store.consume_fuel(u64::MAX).is_err()); - assert!(store.consume_fuel(i64::MAX as u64 + 1).is_err()); - assert_eq!(store.consume_fuel(i64::MAX as u64).unwrap(), 0); + store.set_fuel(u64::MAX).unwrap(); + assert_eq!(store.get_fuel().unwrap(), u64::MAX); } #[test] @@ -227,8 +204,8 @@ fn unconditionally_trapping_memory_accesses_save_fuel_before_trapping() { let mut store = Store::new(&engine, ()); let init_fuel = 1_000; - store.add_fuel(init_fuel).unwrap(); - assert_eq!(init_fuel, store.fuel_remaining().unwrap()); + store.set_fuel(init_fuel).unwrap(); + assert_eq!(init_fuel, store.get_fuel().unwrap()); let instance = Instance::new(&mut store, &module, &[]).unwrap(); let f = instance @@ -240,7 +217,6 @@ fn unconditionally_trapping_memory_accesses_save_fuel_before_trapping() { // The `i32.add` consumed some fuel before the unconditionally trapping // memory access. - let consumed_fuel = store.fuel_consumed().unwrap(); + let consumed_fuel = init_fuel - store.get_fuel().unwrap(); assert!(consumed_fuel > 0); - assert_eq!(init_fuel, consumed_fuel + store.fuel_remaining().unwrap()); }