Repository: bytecodealliance/wasmtime
Pull request #7298 (2023-10-19 21:23:21)

New Fuel APIs for Store
In an effort to simplify the many fuel related APIs, simplify the
interface here to a single counter with get and set methods.
Additionally the async yield is reduced to an interval of the total fuel
instead of injecting fuel, so it's easy to still reason about how much
fuel is left even with yielding turned on.

Internally this works by keeping two counters - one the VM uses to
increment towards 0 for fuel, the other to track how much is in
"reserve". Then when we're out of gas, we pull from the reserve to
refuel and continue. We use the reserve in two cases: one for overflow
of the fuel (which is an i64 and the API expresses fuel as u64) and the
other for async yieling, which then the yield interval acts as a cap to
how much we can refuel with.

This also means that `get_fuel` can return the full range of `u64`
before this change it could only return up to `i64::MAX`. This is
important because this PR is removing the functionality to track fuel
consumption, and this makes the API less error prone for embedders to
track consumption themselves.

Careful to note that the VM counter that is stored as `i64` can be
positive if an instruction "costs" multiple units of fuel when the fuel
ran out.

Fixes: #7255

I've implemented the APIs as documented in the strawman from the issue, 
but I used `Option<NonZeroU64>` for the yield interval instead of trying
to figure out what to do if the yield interval is `0`.



rockwotj: I'm happy to drop the second commit and not try to fix the full resolution if there is a desire to keep the same semantics there (I'm assuming it's OK if there no async support to still return `Ok` from the `out_of_gas` function in `Store`).
github-actions[bot]: #### Subscribe to Label Action

cc @fitzgen, @peterhuene

<details>
This issue or pull request has been labeled: "fuzzing", "wasmtime:api", "wasmtime:c-api"

Thus the following users have been cc'd because of the following labels:

* fitzgen: fuzzing
* peterhuene: wasmtime:api, wasmtime:c-api

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>
github-actions[bot]: #### Label Messager: wasmtime:config

It looks like you are changing Wasmtime's configuration options. Make sure to
complete this check list:

* [ ] If you added a new `Config` method, you wrote extensive documentation for
      it.

  <details>

  Our documentation should be of the following form:

  ```text
  Short, simple summary sentence.

  More details. These details can be multiple paragraphs. There should be
  information about not just the method, but its parameters and results as
  well.

  Is this method fallible? If so, when can it return an error?

  Can this method panic? If so, when does it panic?

  # Example

  Optional example here.
  ```

  </details>

* [ ] If you added a new `Config` method, or modified an existing one, you
  ensured that this configuration is exercised by the fuzz targets.

  <details>

  For example, if you expose a new strategy for allocating the next instance
  slot inside the pooling allocator, you should ensure that at least one of our
  fuzz targets exercises that new strategy.

  Often, all that is required of you is to ensure that there is a knob for this
  configuration option in [`wasmtime_fuzzing::Config`][fuzzing-config] (or one
  of its nested `struct`s).

  Rarely, this may require authoring a new fuzz target to specifically test this
  configuration. See [our docs on fuzzing][fuzzing-docs] for more details.

  </details>

* [ ] If you are enabling a configuration option by default, make sure that it
  has been fuzzed for at least two weeks before turning it on by default.

[fuzzing-config]: https://github.com/bytecodealliance/wasmtime/blob/ca0e8d0a1d8cefc0496dba2f77a670571d8fdcab/crates/fuzzing/src/generators.rs#L182-L194
[fuzzing-docs]: https://docs.wasmtime.dev/contributing-fuzzing.html


--------------------------------------------------------------------------------

<details>

To modify this label's message, edit the <code>.github/label-messager/wasmtime-config.md</code> file.

To add new label messages or remove existing label messages, edit the
<code>.github/label-messager.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/label-messager-action)

</details>
alexcrichton: I'll take a look once CI is green, so feel free to ping me when that comes up. I'll check in every now and then as well.
rockwotj: @alexcrichton test are passing (no rush on a review, I wont be able to make updates until Monday anyways)
alexcrichton: oops sorry queued up for merge a bit too soon there

Patch:
diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs
index a2ec33a9f..bb4f6ed55 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 955944c72..1b490003a 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 ac67d1a1d..740d98a33 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 60cf7d308..ffd9b10c1 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<NonZeroU64>,
+) -> Option<Box<wasmtime_error_t>> {
+    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 d6c95e00c..844357fb2 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<Box<wasmtime_error_t>> {
-    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<Box<wasmtime_error_t>> {
-    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<Box<wasmtime_error_t>> {
-    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 74b0bfb72..a5914ee36 100644
--- a/crates/fuzzing/src/generators/config.rs
+++ b/crates/fuzzing/src/generators/config.rs
@@ -260,7 +260,7 @@ impl Config {
     pub fn configure_store(&self, store: &mut Store<StoreLimits>) {
         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 11910cc0b..844654b4e 100644
--- a/crates/fuzzing/src/oracles.rs
+++ b/crates/fuzzing/src/oracles.rs
@@ -145,7 +145,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
@@ -598,7 +598,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);
@@ -836,20 +836,6 @@ impl Drop for SignalOnDrop {
     }
 }
 
-/// Set the amount of fuel in a store to a given value
-pub fn set_fuel<T>(store: &mut Store<T>, 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 31bc85e41..1e26adb2c 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 a551b64da..3a23d51d4 100644
--- a/crates/wasmtime/src/func.rs
+++ b/crates/wasmtime/src/func.rs
@@ -1895,50 +1895,26 @@ impl<T> 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<u64> {
-        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<u64> {
-        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<u64> {
+        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<u64> {
-        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<u64>) -> Result<()> {
+        self.store.fuel_async_yield_interval(interval)
     }
 }
 
diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs
index e4745d3ad..edc6d2107 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<NonZeroU64>,
     /// 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<T> Store<T> {
     /// Creates a new [`Store`] to be associated with the given [`Engine`] and
     /// `data` provided.
@@ -498,13 +491,13 @@ impl<T> Store<T> {
                 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<T> Store<T> {
         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<u64> {
-        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<u64> {
-        self.inner.fuel_remaining()
+    /// originally configured via [`Store::set_fuel`].
+    pub fn get_fuel(&self) -> Result<u64> {
+        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<T> Store<T> {
     /// 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<u64> {
-        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<T> Store<T> {
     ///
     /// 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<T> Store<T> {
     /// 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<u64>) -> 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<u64> {
-        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<u64> {
-        self.0.fuel_remaining()
+    /// For more information see [`Store::get_fuel`].
+    pub fn get_fuel(&self) -> Result<u64> {
+        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<u64> {
-        self.0.fuel_consumed()
-    }
-
     /// Returns remaining fuel in this store.
     ///
-    /// For more information see [`Store::fuel_remaining`]
-    pub fn fuel_remaining(&self) -> Option<u64> {
-        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<u64> {
-        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<u64> {
+        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<u64>) -> 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<T> StoreInner<T> {
     }
 }
 
+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<NonZeroU64>,
+) -> 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<NonZeroU64>,
+    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<u64> {
-        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<u64> {
+        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<u64> {
-        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<u64>) -> 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<u64> {
-        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<T> wasmtime_runtime::Store for StoreInner<T> {
         }
     }
 
-    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<u64, anyhow::Error> {
@@ -2398,3 +2264,125 @@ impl<T: Copy> Drop for Reset<T> {
         }
     }
 }
+
+#[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<NonZeroU64>,
+    }
+
+    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 2656e73d1..a5feae115 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 9b0eaa6b0..173bdcf58 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 e6f4c74ec..179c68d1c 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::<i32, i32>(&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 e90f6de97..2073f3888 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 dfd24a002..df2fd673d 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -171,7 +171,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 ce9c2560a..a064100a8 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)


Test patch:
diff --git a/tests/all/async_functions.rs b/tests/all/async_functions.rs
index f2cac9ffd..3796c9b05 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 116ced19c..cbb398fdd 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<u8>,), ()>(&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 df046763c..6686daf5c 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::<Trap>().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());
 }