Repository: bytecodealliance/wasmtime
Pull request #7338 (2023-10-23 18:46:29)

Fix native feature detect in "min" builds
With a custom standard library disabling all features means disabling some support for feature-detection macros of the native platform. This meant that `wasmtime compile` output locally wasn't runnable in `wasmtime-min run` because it couldn't correctly detect that features were in fact available.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/ci/build-release-artifacts.sh b/ci/build-release-artifacts.sh
index 3b3b2b1ab..71c788839 100755
--- a/ci/build-release-artifacts.sh
+++ b/ci/build-release-artifacts.sh
@@ -28,7 +28,7 @@ if [[ "$build" = *-min ]]; then
   export RUSTFLAGS=-Zlocation-detail=none
   export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1
   export CARGO_PROFILE_RELEASE_LTO=true
-  flags="-Zbuild-std=std,panic_abort --no-default-features -Zbuild-std-features="
+  flags="-Zbuild-std=std,panic_abort --no-default-features -Zbuild-std-features=std_detect_dlsym_getauxval"
   flags="$flags --features disable-logging"
 else
   # For release builds the CLI is built a bit more feature-ful than the Cargo
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());
 }