1use std::future::Future;
24
25use wasmtime::{AsContextMut, Caller, Func, FuncType, Trap, Val};
26
27use super::StoreData;
28
29#[derive(Clone, Copy, Debug)]
33pub struct Rate {
34 fuel: u64,
35 per: u64,
36}
37
38impl Rate {
39 const fn per_unit(fuel: u64) -> Rate {
40 Rate { fuel, per: 1 }
41 }
42
43 const fn per_units(fuel: u64, per: u64) -> Rate {
44 Rate { fuel, per }
45 }
46
47 pub fn cost(self, n: u64) -> u64 {
49 n.saturating_mul(self.fuel).div_ceil(self.per)
50 }
51}
52
53pub const CALL: u64 = 16;
57pub const COPY: Rate = Rate::per_units(1, 8);
59pub const SCAN: Rate = Rate::per_unit(1);
62pub const PARSE: Rate = Rate::per_unit(6);
65pub const ELEM: Rate = Rate::per_unit(10);
67pub const REGEX: Rate = Rate::per_unit(8);
71pub const REGEX_COMPILE: u64 = 2_000;
74pub const HASH: Rate = Rate::per_unit(4);
76pub const IO: Rate = Rate::per_units(1, 4);
78pub const SYSCALL: Rate = Rate::per_unit(400);
80pub const TZ: u64 = 200;
82pub const GATE: u64 = 400;
84
85pub fn sort_cost(n: u64) -> u64 {
88 let log = u64::from(n.max(2).ilog2());
89 ELEM.cost(n.saturating_mul(log))
90}
91
92pub fn bigint_product_cost(a: u64, b: u64) -> u64 {
97 let (small, large) = (a.min(b).max(1), a.max(b).max(1));
98 let schoolbook = small.saturating_mul(large.min(64));
99 let beyond = large
100 .saturating_sub(64)
101 .saturating_mul(u64::from(large.ilog2()) + 1);
102 ELEM.cost(schoolbook.saturating_add(beyond))
103}
104
105pub fn charge_call(ctx: impl AsContextMut<Data = StoreData>) -> wasmtime::Result<()> {
107 charge_host_fuel(ctx, CALL)
108}
109
110pub fn charge(
112 ctx: impl AsContextMut<Data = StoreData>,
113 rate: Rate,
114 n: u64,
115) -> wasmtime::Result<()> {
116 charge_host_fuel(ctx, rate.cost(n))
117}
118
119pub(crate) fn host_func(
123 mut store: impl AsContextMut<Data = StoreData>,
124 ty: FuncType,
125 body: impl Fn(&mut Caller<'_, StoreData>, &[Val], &mut [Val]) -> wasmtime::Result<()>
126 + Send
127 + Sync
128 + 'static,
129) -> Func {
130 let abi = ty.clone();
131 Func::new(&mut store, ty, move |mut caller, params, results| {
132 super::host::check_host_abi(&abi, params, results)?;
133 charge_call(&mut caller)?;
134 body(&mut caller, params, results)
135 })
136}
137
138pub(crate) fn host_func_async<F>(
141 mut store: impl AsContextMut<Data = StoreData>,
142 ty: FuncType,
143 body: F,
144) -> Func
145where
146 F: for<'a> Fn(
147 Caller<'a, StoreData>,
148 &'a [Val],
149 &'a mut [Val],
150 ) -> Box<dyn Future<Output = wasmtime::Result<()>> + Send + 'a>
151 + Send
152 + Sync
153 + 'static,
154{
155 let abi = ty.clone();
156 Func::new_async(&mut store, ty, move |mut caller, params, results| {
157 if let Err(error) = super::host::check_host_abi(&abi, params, results) {
158 return Box::new(async move { Err(error) });
159 }
160 if let Err(error) = charge_call(&mut caller) {
161 return Box::new(async move { Err(error) });
162 }
163 body(caller, params, results)
164 })
165}
166
167pub fn settle(
172 ctx: impl AsContextMut<Data = StoreData>,
173 rate: Rate,
174 n: u64,
175) -> wasmtime::Result<()> {
176 settle_host_fuel(ctx, rate.cost(n))
177}
178
179pub(crate) fn settle_result<T, C: AsContextMut<Data = StoreData>>(
183 caller: &mut C,
184 body: impl FnOnce(&mut C) -> wasmtime::Result<T>,
185) -> wasmtime::Result<T> {
186 let previous = caller.as_context().data().settling_host_result;
187 caller.as_context_mut().data_mut().settling_host_result = true;
188 let result = body(caller);
189 caller.as_context_mut().data_mut().settling_host_result = previous;
190 result
191}
192
193pub fn settle_host_fuel(
195 mut ctx: impl AsContextMut<Data = StoreData>,
196 units: u64,
197) -> wasmtime::Result<()> {
198 let mut ctx = ctx.as_context_mut();
199 let pending = ctx.data().host_fuel_pending;
200 let available = ctx.get_fuel()?.saturating_sub(pending);
201 if units <= available {
202 return charge_host_fuel(ctx, units);
203 }
204 ctx.set_fuel(0)?;
205 let data = ctx.data_mut();
206 data.host_fuel_pending = 0;
207 data.host_fuel = data.host_fuel.saturating_add(available);
208 Ok(())
209}
210
211pub const HOST_FUEL_BATCH: u64 = 10_000;
217pub const HOST_FUEL_PENDING_CAP: u64 = 1_000 * HOST_FUEL_BATCH;
222
223pub fn charge_host_fuel(
234 mut ctx: impl AsContextMut<Data = StoreData>,
235 units: u64,
236) -> wasmtime::Result<()> {
237 if units == 0 {
238 return Ok(());
239 }
240 let mut ctx = ctx.as_context_mut();
241 let pending = ctx.data().host_fuel_pending;
242 let engine_fuel = ctx.get_fuel()?;
243 let available = engine_fuel.saturating_sub(pending);
244 if units > available {
245 ctx.set_fuel(0)?;
246 let data = ctx.data_mut();
247 data.host_fuel_pending = 0;
248 data.host_fuel = data.host_fuel.saturating_add(available);
249 if data.settling_host_result {
250 return Ok(());
251 }
252 return Err(Trap::OutOfFuel.into());
253 }
254 let batch = pending + units;
256 let data = ctx.data_mut();
257 data.host_fuel = data.host_fuel.saturating_add(units);
258 let wasm_burned = data
263 .host_fuel_applied_at
264 .map_or(u64::MAX, |mark| mark.saturating_sub(engine_fuel));
265 let apply = batch >= HOST_FUEL_PENDING_CAP
266 || (batch >= HOST_FUEL_BATCH && wasm_burned > HOST_FUEL_BATCH);
267 if !apply {
268 data.host_fuel_pending = batch;
269 return Ok(());
270 }
271 data.host_fuel_pending = 0;
272 data.host_fuel_applied_at = Some(engine_fuel - batch);
273 ctx.set_fuel(engine_fuel - batch)
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use crate::runtime::{RuntimeConfig, Vfs};
280 use wasmtime::{Func, FuncType, Val, ValType};
281
282 #[test]
283 fn result_settlement_preserves_values_and_restores_refusing_charges() {
284 let config = RuntimeConfig::default();
285 let engine = config.engine().unwrap();
286 let mut store = config
287 .store(&engine, StoreData::with_vfs(Vfs::none()))
288 .unwrap();
289 let complete = Func::new(
290 &mut store,
291 FuncType::new(&engine, [], [ValType::I32]),
292 |mut caller, _, results| {
293 results[0] = settle_result(&mut caller, |caller| {
294 charge(caller, PARSE, 100)?;
295 Ok(Val::I32(42))
296 })?;
297 assert!(!caller.data().settling_host_result);
298 Ok(())
299 },
300 );
301 store.set_fuel(1).unwrap();
302 let mut results = [Val::I32(0)];
303 complete.call(&mut store, &[], &mut results).unwrap();
304 assert_eq!(results[0].i32(), Some(42));
305 assert_eq!(store.get_fuel().unwrap(), 0);
306 assert_eq!(store.data().host_fuel, 1);
307 assert!(charge_call(&mut store).unwrap_err().is::<Trap>());
308
309 let fail = Func::new(
310 &mut store,
311 FuncType::new(&engine, [], []),
312 |mut caller, _, _| {
313 let result = settle_result(&mut caller, |caller| {
314 charge(caller, COPY, 100)?;
315 Err::<(), _>(crate::runtime::host::range_error("copy entry limit"))
316 });
317 assert!(!caller.data().settling_host_result);
318 result
319 },
320 );
321 let error = fail.call(&mut store, &[], &mut []).unwrap_err();
322 assert!(error.to_string().contains("copy entry limit"), "{error}");
323 assert!(!store.data().settling_host_result);
324 assert!(charge_call(&mut store).unwrap_err().is::<Trap>());
325 }
326
327 #[test]
328 fn settling_never_refuses_and_leaves_a_short_budget_at_zero() {
329 let config = RuntimeConfig::default();
330 let engine = config.engine().unwrap();
331 let mut store = config
332 .store(&engine, StoreData::with_vfs(Vfs::none()))
333 .unwrap();
334 let settle = Func::new(
335 &mut store,
336 FuncType::new(&engine, [ValType::I64], []),
337 |mut caller, params, _| settle_host_fuel(&mut caller, params[0].unwrap_i64() as u64),
338 );
339 let charge = Func::new(
340 &mut store,
341 FuncType::new(&engine, [ValType::I64], []),
342 |mut caller, params, _| charge_host_fuel(&mut caller, params[0].unwrap_i64() as u64),
343 );
344
345 store.set_fuel(1_000).unwrap();
347 settle.call(&mut store, &[Val::I64(300)], &mut []).unwrap();
348 assert_eq!(store.get_fuel().unwrap(), 1_000);
349 assert_eq!(store.data().host_fuel_pending, 300);
350 assert_eq!(store.data().host_fuel, 300);
351
352 settle
355 .call(&mut store, &[Val::I64(5_000)], &mut [])
356 .unwrap();
357 assert_eq!(store.get_fuel().unwrap(), 0);
358 assert_eq!(store.data().host_fuel_pending, 0);
359 assert_eq!(store.data().host_fuel, 1_000);
360 let error = charge
361 .call(&mut store, &[Val::I64(1)], &mut [])
362 .unwrap_err();
363 assert_eq!(error.downcast_ref::<Trap>(), Some(&Trap::OutOfFuel));
364 }
365
366 #[test]
367 fn the_batch_covers_the_default_yield_interval() {
368 assert!(RuntimeConfig::default().async_yield_fuel <= Some(HOST_FUEL_BATCH));
369 }
370
371 #[test]
372 fn rates_round_up_and_never_make_work_free() {
373 assert_eq!(COPY.cost(0), 0);
374 assert_eq!(COPY.cost(1), 1);
375 assert_eq!(COPY.cost(8), 1);
376 assert_eq!(COPY.cost(9), 2);
377 assert_eq!(PARSE.cost(3), 18);
378 assert_eq!(SCAN.cost(u64::MAX), u64::MAX);
379 assert_eq!(sort_cost(0), 0);
380 assert_eq!(sort_cost(1), ELEM.cost(1));
381 assert_eq!(sort_cost(1024), ELEM.cost(10 * 1024));
382 }
383
384 #[test]
385 fn host_charges_are_batched_counted_and_refused_before_the_work() {
386 use crate::runtime::limits::ExecutionUsage;
387 let config = RuntimeConfig::default();
388 let engine = config.engine().unwrap();
389 let mut store = config
390 .store(&engine, StoreData::with_vfs(Vfs::none()))
391 .unwrap();
392 let charge = Func::new(
393 &mut store,
394 FuncType::new(&engine, [ValType::I64], []),
395 |mut caller, params, _| charge_host_fuel(&mut caller, params[0].unwrap_i64() as u64),
396 );
397 let charge_units = |store: &mut wasmtime::Store<StoreData>, units: u64| {
398 charge.call(&mut *store, &[Val::I64(units as i64)], &mut [])
399 };
400 let burn = |store: &mut wasmtime::Store<StoreData>, units: u64| {
402 let left = store.get_fuel().unwrap() - units;
403 store.set_fuel(left).unwrap();
404 };
405
406 store.set_fuel(100_000).unwrap();
409 charge_units(&mut store, 9_999).unwrap();
410 assert_eq!(store.get_fuel().unwrap(), 100_000);
411 assert_eq!(store.data().host_fuel, 9_999);
412 assert_eq!(store.data().host_fuel_pending, 9_999);
413 let usage = ExecutionUsage::capture(&store, 100_000).unwrap();
414 assert_eq!(
415 (usage.fuel, usage.wasm_fuel, usage.host_fuel),
416 (9_999, 0, 9_999)
417 );
418 charge_units(&mut store, 1).unwrap();
420 assert_eq!(store.get_fuel().unwrap(), 90_000);
421 assert_eq!(store.data().host_fuel_pending, 0);
422 charge_units(&mut store, 20_000).unwrap();
425 assert_eq!(store.get_fuel().unwrap(), 90_000);
426 assert_eq!(store.data().host_fuel_pending, 20_000);
427 burn(&mut store, 10_000);
428 charge_units(&mut store, 1).unwrap();
429 assert_eq!(store.get_fuel().unwrap(), 80_000);
430 burn(&mut store, 1);
431 charge_units(&mut store, 1).unwrap();
432 assert_eq!(store.get_fuel().unwrap(), 59_997);
433 assert_eq!(store.data().host_fuel_pending, 0);
434 let charged_so_far = store.data().host_fuel;
435
436 store.set_fuel(300).unwrap();
440 charge_units(&mut store, 100).unwrap();
441 let error = charge_units(&mut store, 201).unwrap_err();
442 assert_eq!(error.downcast_ref::<Trap>(), Some(&Trap::OutOfFuel));
443 assert_eq!(store.get_fuel().unwrap(), 0);
444 assert_eq!(store.data().host_fuel, charged_so_far + 100 + 200);
445 assert_eq!(store.data().host_fuel_pending, 0);
446
447 store.set_fuel(1_000).unwrap();
451 store.data_mut().host_fuel = 0;
452 charge_units(&mut store, 600).unwrap();
453 burn(&mut store, 700);
454 let error = charge_units(&mut store, 1).unwrap_err();
455 assert_eq!(error.downcast_ref::<Trap>(), Some(&Trap::OutOfFuel));
456 assert_eq!(store.data().host_fuel, 600);
457 let usage = ExecutionUsage::capture(&store, 1_000).unwrap();
458 assert_eq!(
459 (usage.fuel, usage.wasm_fuel, usage.host_fuel),
460 (1_000, 400, 600)
461 );
462
463 store.set_fuel(50).unwrap();
465 charge_units(&mut store, 0).unwrap();
466 assert_eq!(store.get_fuel().unwrap(), 50);
467 }
468}