rockwotj opened issue #7255:
Feature & Benefit
Simplify the fuel related APIs.
Noted in https://github.com/bytecodealliance/wasmtime/pull/7240#issuecomment-1762549864, there are a number of fuel related APIs and they have grown organically - we should take a step back and see if there is a simpler/smaller scoped API that can serve the same purpose.
Proposal
Simplify fuel by removing "consumption" related tracking (with the right primitives embedders should be able to do this themselves).
Remove the following APIs:
pub fn fuel_consumed(&self) -> Option<u64> pub fn fuel_remaining(&mut self) -> Option<u64> pub fn add_fuel(&mut self, fuel: u64) -> Result<()> pub fn consume_fuel(&mut self, fuel: u64) -> Result<u64> pub fn out_of_fuel_trap(&mut self) pub fn out_of_fuel_async_yield( &mut self, injection_count: u64, fuel_to_inject: u64 )
Introduce the following APIs:
// All APIs only return error if fuel is not enabled. // get the current amount of fuel pub fn get_fuel() -> Result<u64> // set the current amount of fuel pub fn set_fuel(u64) -> Result<()> // in async mode, the engine should yield every N fuel consumed, or None to remove yielding // Additionally Some(0) should return an error? pub fn fuel_async_yield_interval(Option<u64>) -> Result<()>
Under the hood, this would work by storing the following state:
let fuel_reserves: u64 = 0; let active_fuel: i64 = 0; // This is what the runtime increments as fuel is consumed let yield_amount: Option<u64> = None;
Then
get_fuel
would be:return (-active_fuel).saturating_add(fuel_reserves)
fuel_async_yield_interval
just assignsyield_amount
then callsset_fuel(get_fuel())
set_fuel
looks something like:if yield_amount.is_none() { self.fuel_reserves = 0; self.active_fuel = -total_fuel; return; } let yield_amount = yield_amount.unwrap(); self.active_fuel = -cmp::min(fuel, yield_amount); self.fuel_reserves = fuel + self.active_fuel;
Then
out_of_gas
does something like:if self.fuel_reserves == 0 { return trap; } set_fuel(get_fuel()); // Easy mode to recalculate active_fuel and fuel_reserves yield();
Alternatives
TODO(reader): Add your ideas :light_bulb: and comments :umm: to this issue :big_smile:
alexcrichton commented on issue #7255:
I like the idea of moving the idea of total consumption out of a
Store
and requiring embedders to track that if necessary. Additionally I like the simplicity here of get/set since that's at least what I'd imagine a counter like this to look like.In that sense going over the existing APIs:
fuel_consumed
- this would need to be replaced by embedders by keeping track of all fuel historically injected. This method would then beall_fuel_ever_injected - get_fuel()
.fuel_remaining
- this would becomeget_fuel()
add_fuel
- this would beset_fuel(get_fuel() + n)
consume_fuel
- this would beget_fuel()
, some conditions, followed by maybeset_fuel
.That all sounds good to me and additionally handles
reset_fuel
added in https://github.com/bytecodealliance/wasmtime/pull/7240 by effectively renaming it toset_fuel
.
rockwotj commented on issue #7255:
Cool - I'm happy to work on this later this week if there is no objections (I'm not sure who all needs/wants to sign off on this sort of thing), and if nobody wants to work on this earlier.
alexcrichton commented on issue #7255:
I'd recommend giving it a day or so for folks to chime in with feedback if they have some, otherwise feel free, and thanks again!
rockwotj commented on issue #7255:
One downside of this API is that we can only store up to
i64
amount of fuel, so calculating consumption can be wrong:set_fuel(u64::MAX).unwrap(); // run a function let consumed = u64::MAX - get_fuel(); // WRONG! We actually started at i64::MAX
Can we change the fuel in vmcontext to be a u64 that goes to zero instead of a negative
i64
that goes to 0?
rockwotj commented on issue #7255:
I guess that is an existing wart:
https://github.com/bytecodealliance/wasmtime/blob/9e4d44626a008f8a1b1b3b4a48f0d5693185844b/tests/all/fuel.rs#L198I guess you always need to compare to
get_fuel
to implement tracking correctly?
rockwotj edited a comment on issue #7255:
One downside of this API is that we can only store up to
i64
amount of fuel, so calculating consumption can be wrong:set_fuel(u64::MAX).unwrap(); // run a function let consumed = u64::MAX - get_fuel(); // WRONG! We actually started at i64::MAX
alexcrichton commented on issue #7255:
Yeah I think it'd be nice if we could fix that but I'm not sure how to do so myself, so in absence of that I think we sort of have to just stick with what we have today beahvior-wise.
alexcrichton closed issue #7255:
Feature & Benefit
Simplify the fuel related APIs.
Noted in https://github.com/bytecodealliance/wasmtime/pull/7240#issuecomment-1762549864, there are a number of fuel related APIs and they have grown organically - we should take a step back and see if there is a simpler/smaller scoped API that can serve the same purpose.
Proposal
Simplify fuel by removing "consumption" related tracking (with the right primitives embedders should be able to do this themselves).
Remove the following APIs:
pub fn fuel_consumed(&self) -> Option<u64> pub fn fuel_remaining(&mut self) -> Option<u64> pub fn add_fuel(&mut self, fuel: u64) -> Result<()> pub fn consume_fuel(&mut self, fuel: u64) -> Result<u64> pub fn out_of_fuel_trap(&mut self) pub fn out_of_fuel_async_yield( &mut self, injection_count: u64, fuel_to_inject: u64 )
Introduce the following APIs:
// All APIs only return error if fuel is not enabled. // get the current amount of fuel pub fn get_fuel() -> Result<u64> // set the current amount of fuel pub fn set_fuel(u64) -> Result<()> // in async mode, the engine should yield every N fuel consumed, or None to remove yielding // Additionally Some(0) should return an error? pub fn fuel_async_yield_interval(Option<u64>) -> Result<()>
Under the hood, this would work by storing the following state:
let fuel_reserves: u64 = 0; let active_fuel: i64 = 0; // This is what the runtime increments as fuel is consumed let yield_amount: Option<u64> = None;
Then
get_fuel
would be:return (-active_fuel).saturating_add(fuel_reserves)
fuel_async_yield_interval
just assignsyield_amount
then callsset_fuel(get_fuel())
set_fuel
looks something like:if yield_amount.is_none() { self.fuel_reserves = 0; self.active_fuel = -total_fuel; return; } let yield_amount = yield_amount.unwrap(); self.active_fuel = -cmp::min(fuel, yield_amount); self.fuel_reserves = fuel + self.active_fuel;
Then
out_of_gas
does something like:if self.fuel_reserves == 0 { return trap; } set_fuel(get_fuel()); // Easy mode to recalculate active_fuel and fuel_reserves yield();
Alternatives
TODO(reader): Add your ideas :light_bulb: and comments :umm: to this issue :big_smile:
Last updated: Dec 23 2024 at 12:05 UTC