From 6e24f081b797cff0b402908d4f035fbc0c99bfbf Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sun, 1 Oct 2023 16:52:00 +0200 Subject: [PATCH] Document budgeting and limiting (#637) --- .github/workflows/site.yml | 5 +- crates/rune-alloc/src/callable.rs | 32 ++++++++ crates/rune-alloc/src/lib.rs | 6 ++ .../rune-alloc/src/{limit.rs => limit/mod.rs} | 74 ++++++++++++++---- crates/rune-alloc/src/slice.rs | 71 +++++++++-------- crates/rune-alloc/src/string/mod.rs | 28 +------ crates/rune-alloc/src/vec/into_iter.rs | 41 +--------- crates/rune-alloc/src/vec/mod.rs | 9 ++- crates/rune-core/src/lib.rs | 4 + crates/rune/src/lib.rs | 3 + crates/rune/src/modules/char.rs | 8 +- crates/rune/src/modules/f64.rs | 17 +++- crates/rune/src/modules/iter.rs | 2 +- crates/rune/src/modules/string.rs | 6 +- crates/rune/src/runtime/budget.rs | 77 +++++++++++++++---- site/templates/macros.html | 8 +- 16 files changed, 241 insertions(+), 150 deletions(-) create mode 100644 crates/rune-alloc/src/callable.rs rename crates/rune-alloc/src/{limit.rs => limit/mod.rs} (77%) diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index b370ed184..1c1fe933b 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -27,13 +27,14 @@ jobs: - run: cargo run --bin rune -- doc --output target/site/docs env: RUST_LOG: rune=info + - run: mdbook build -d ../target/site/book book - uses: dtolnay/rust-toolchain@nightly - - run: cargo +nightly doc -p rune --all-features --target-dir target/site/api + - run: cargo +nightly doc -p rune --all-features env: RUST_LOG: rune=info RUSTFLAGS: --cfg docsrs RUSTDOCFLAGS: --cfg docsrs - - run: mdbook build -d ../target/site/book book + - run: mv target/doc target/site/api - uses: peaceiris/actions-gh-pages@v3 with: deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} diff --git a/crates/rune-alloc/src/callable.rs b/crates/rune-alloc/src/callable.rs new file mode 100644 index 000000000..7b4c106fc --- /dev/null +++ b/crates/rune-alloc/src/callable.rs @@ -0,0 +1,32 @@ +//! A trait used for types which can be called. +//! +//! This trait allows for memory [`limits`] and [`budgets`] to be combined. +//! +//! [`limits`]: crate::limit +//! [`budgets`]: ../../runtime/budget/index.html + +/// A trait used for types which can be called. +/// +/// This trait allows for memory [`limits`] and [`budgets`] to be combined. +/// +/// [`limits`]: crate::limit +/// [`budgets`]: ../../runtime/budget/index.html +pub trait Callable { + /// Output of the callable. + type Output; + + /// Call and consume the callable. + fn call(self) -> Self::Output; +} + +/// Blanket implementation for closures. +impl Callable for T +where + T: FnOnce() -> O, +{ + type Output = O; + + fn call(self) -> Self::Output { + self() + } +} diff --git a/crates/rune-alloc/src/lib.rs b/crates/rune-alloc/src/lib.rs index 01b1cb3d9..96bd01852 100644 --- a/crates/rune-alloc/src/lib.rs +++ b/crates/rune-alloc/src/lib.rs @@ -36,6 +36,10 @@ #![no_std] // TODO: get rid of this once we've evaluated what we want to have public. #![allow(dead_code)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_doc_tests)] +#![cfg_attr(rune_nightly, feature(rustdoc_missing_doc_code_examples))] +#![cfg_attr(rune_nightly, deny(rustdoc::missing_doc_code_examples))] #![cfg_attr(rune_nightly, feature(core_intrinsics))] #![cfg_attr(rune_nightly, feature(dropck_eyepatch))] #![cfg_attr(rune_nightly, feature(min_specialization))] @@ -135,6 +139,8 @@ pub(crate) mod ptr; #[doc(hidden)] pub mod slice; +pub mod callable; + pub mod prelude { //! Prelude for common traits used in combination with this crate which //! matches the behavior of the std prelude. diff --git a/crates/rune-alloc/src/limit.rs b/crates/rune-alloc/src/limit/mod.rs similarity index 77% rename from crates/rune-alloc/src/limit.rs rename to crates/rune-alloc/src/limit/mod.rs index d1539cd05..94c449c23 100644 --- a/crates/rune-alloc/src/limit.rs +++ b/crates/rune-alloc/src/limit/mod.rs @@ -1,12 +1,29 @@ //! Memory limits for Rune. //! //! This module contains methods which allows for limiting the memory use of the -//! virtual machine to abide by the specified budget. +//! virtual machine to abide by the specified memory limit. //! //! By default memory limits are disabled, but can be enabled by wrapping your //! function call or future in [with]. +//! +//! # Limitations +//! +//! Limiting is plugged in at the [Rust allocator level], and does not account +//! for allocator overhead. Allocator overhead comes about because an allocator +//! needs to use some extra system memory to perform internal bookkeeping. +//! Usually this should not be an issue, because the allocator overhead should +//! be a fragment of memory use. But the exact details would depend on the +//! [global allocator] used. +//! +//! As an example, see the [implementation notes for jemalloc]. +//! +//! [implementation notes for jemalloc]: +//! http://jemalloc.net/jemalloc.3.html#implementation_notes +//! [Rust allocator level]: https://doc.rust-lang.org/alloc/alloc/index.html +//! [global allocator]: +//! https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html -#[cfg_attr(feature = "std", path = "limit/std.rs")] +#[cfg_attr(feature = "std", path = "std.rs")] mod no_std; use core::future::Future; @@ -15,17 +32,32 @@ use core::task::{Context, Poll}; use pin_project::pin_project; +use crate::callable::Callable; + /// Something being budgeted. +/// +/// See [`with`]. #[pin_project] pub struct Memory { - /// The current budget. - budget: usize, + /// The current limit. + memory: usize, /// The thing being budgeted. #[pin] value: T, } -/// Wrap the given value with a memory limit. +/// Wrap the given value with a memory limit. Using a value of [`usize::MAX`] +/// effectively disables the memory limit. +/// +/// The following things can be wrapped: +/// * A [`FnOnce`] closure, like `with(|| println!("Hello World")).call()`. +/// * A [`Future`], like `with(async { /* async work */ }).await`; +/// +/// It's also possible to wrap other wrappers which implement [`Callable`]. +/// +/// See the [module level documentation] for more details. +/// +/// [module level documentation]: crate::limit /// /// # Examples /// @@ -48,7 +80,7 @@ pub struct Memory { /// # Ok::<_, rune::alloc::Error>(()) /// ``` /// -/// Overloading the limit. Note that this happens because while the vector is +/// Breaching the limit. Note that this happens because while the vector is /// growing it might both over-allocate, and hold onto two allocations /// simultaneously. /// @@ -68,8 +100,8 @@ pub struct Memory { /// /// assert!(f.call().is_err()); /// ``` -pub fn with(budget: usize, value: T) -> Memory { - Memory { budget, value } +pub fn with(memory: usize, value: T) -> Memory { + Memory { memory, value } } /// Get remaining memory that may be allocated. @@ -122,9 +154,9 @@ impl Drop for MemoryGuard { } } -impl Memory +impl Memory where - T: FnOnce() -> O, + T: Callable, { /// Call the wrapped function, replacing the current budget and restoring it /// once the function call completes. @@ -193,9 +225,21 @@ where /// assert_eq!(limit::get(), usize::MAX); /// # Ok::<_, rune::alloc::Error>(()) /// ``` - pub fn call(self) -> O { - let _guard = MemoryGuard(self::no_std::rune_memory_replace(self.budget)); - (self.value)() + pub fn call(self) -> T::Output { + Callable::call(self) + } +} + +impl Callable for Memory +where + T: Callable, +{ + type Output = T::Output; + + #[inline] + fn call(self) -> Self::Output { + let _guard = MemoryGuard(self::no_std::rune_memory_replace(self.memory)); + self.value.call() } } @@ -281,9 +325,9 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); - let _guard = MemoryGuard(self::no_std::rune_memory_replace(*this.budget)); + let _guard = MemoryGuard(self::no_std::rune_memory_replace(*this.memory)); let poll = this.value.poll(cx); - *this.budget = self::no_std::rune_memory_get(); + *this.memory = self::no_std::rune_memory_get(); poll } } diff --git a/crates/rune-alloc/src/slice.rs b/crates/rune-alloc/src/slice.rs index 55f965bf7..76b63fc42 100644 --- a/crates/rune-alloc/src/slice.rs +++ b/crates/rune-alloc/src/slice.rs @@ -1,4 +1,4 @@ -pub(crate) use self::iter::{RawIter, RawIterMut}; +pub use self::iter::{RawIter, RawIterMut}; pub(crate) mod iter; use crate::alloc::{Allocator, Global}; @@ -136,46 +136,49 @@ pub(crate) mod hack { where T: TryClone, { - #[inline] - fn to_vec(s: &[Self], alloc: A) -> Result, Error> { - struct DropGuard<'a, T, A: Allocator> { - vec: &'a mut Vec, - num_init: usize, - } - impl<'a, T, A: Allocator> Drop for DropGuard<'a, T, A> { - #[inline] - fn drop(&mut self) { - // SAFETY: - // items were marked initialized in the loop below - unsafe { - self.vec.set_len(self.num_init); + default_fn! { + #[inline] + fn to_vec(s: &[Self], alloc: A) -> Result, Error> { + struct DropGuard<'a, T, A: Allocator> { + vec: &'a mut Vec, + num_init: usize, + } + + impl<'a, T, A: Allocator> Drop for DropGuard<'a, T, A> { + #[inline] + fn drop(&mut self) { + // SAFETY: + // items were marked initialized in the loop below + unsafe { + self.vec.set_len(self.num_init); + } } } + let mut vec = Vec::try_with_capacity_in(s.len(), alloc)?; + let mut guard = DropGuard { + vec: &mut vec, + num_init: 0, + }; + let slots = guard.vec.spare_capacity_mut(); + // .take(slots.len()) is necessary for LLVM to remove bounds checks + // and has better codegen than zip. + for (i, b) in s.iter().enumerate().take(slots.len()) { + guard.num_init = i; + slots[i].write(b.try_clone()?); + } + core::mem::forget(guard); + // SAFETY: + // the vec was allocated and initialized above to at least this length. + unsafe { + vec.set_len(s.len()); + } + Ok(vec) } - let mut vec = Vec::try_with_capacity_in(s.len(), alloc)?; - let mut guard = DropGuard { - vec: &mut vec, - num_init: 0, - }; - let slots = guard.vec.spare_capacity_mut(); - // .take(slots.len()) is necessary for LLVM to remove bounds checks - // and has better codegen than zip. - for (i, b) in s.iter().enumerate().take(slots.len()) { - guard.num_init = i; - slots[i].write(b.try_clone()?); - } - core::mem::forget(guard); - // SAFETY: - // the vec was allocated and initialized above to at least this length. - unsafe { - vec.set_len(s.len()); - } - Ok(vec) } } #[cfg(rune_nightly)] - impl ConvertVec for T { + impl ConvertVec for T { #[inline] fn to_vec(s: &[Self], alloc: A) -> Result, Error> { let mut v = Vec::try_with_capacity_in(s.len(), alloc)?; diff --git a/crates/rune-alloc/src/string/mod.rs b/crates/rune-alloc/src/string/mod.rs index fc910f47f..f11447c94 100644 --- a/crates/rune-alloc/src/string/mod.rs +++ b/crates/rune-alloc/src/string/mod.rs @@ -535,24 +535,6 @@ impl PartialEq for FromUtf8Error { impl Eq for FromUtf8Error {} -/// A possible error value when converting a `String` from a UTF-16 byte slice. -/// -/// This type is the error type for the [`from_utf16`] method on [`String`]. -/// -/// [`from_utf16`]: String::from_utf16 -/// -/// # Examples -/// -/// ``` -/// // 𝄞muic -/// let v = &[0xD834, 0xDD1E, 0x006d, 0x0075, -/// 0xD800, 0x0069, 0x0063]; -/// -/// assert!(String::from_utf16(v).is_err()); -/// ``` -#[derive(Debug)] -pub(crate) struct FromUtf16Error(()); - impl String { /// Creates a new empty `String`. /// @@ -1087,7 +1069,7 @@ impl String { /// ``` #[inline] #[must_use] - pub(crate) fn as_bytes(&self) -> &[u8] { + pub fn as_bytes(&self) -> &[u8] { &self.vec } @@ -1790,16 +1772,8 @@ impl fmt::Display for FromUtf8Error { } } -impl fmt::Display for FromUtf16Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt("invalid utf-16: lone surrogate found", f) - } -} - #[cfg(feature = "std")] impl std::error::Error for FromUtf8Error {} -#[cfg(feature = "std")] -impl std::error::Error for FromUtf16Error {} impl TryClone for String { fn try_clone(&self) -> Result { diff --git a/crates/rune-alloc/src/vec/into_iter.rs b/crates/rune-alloc/src/vec/into_iter.rs index cbb499e56..e39555320 100644 --- a/crates/rune-alloc/src/vec/into_iter.rs +++ b/crates/rune-alloc/src/vec/into_iter.rs @@ -82,47 +82,8 @@ impl IntoIter { ptr::slice_from_raw_parts_mut(self.ptr as *mut T, self.len()) } - /// Drops remaining elements and relinquishes the backing allocation. This - /// method guarantees it won't panic before relinquishing the backing - /// allocation. - /// - /// This is roughly equivalent to the following, but more efficient - /// - /// ``` - /// use rune::alloc::Vec; - /// - /// # #[cfg(not(miri))] - /// # fn main() -> Result<(), rune_alloc::Error> { - /// # let mut into_iter = Vec::::try_with_capacity(10)?.into_iter(); - /// let mut into_iter = std::mem::replace(&mut into_iter, Vec::new().into_iter()); - /// (&mut into_iter).for_each(drop); - /// std::mem::forget(into_iter); - /// # Ok(()) - /// # } - /// # #[cfg(miri)] fn main() {} - /// ``` - /// - /// This method is used by in-place iteration, refer to the vec::in_place_collect - /// documentation for an overview. - pub(super) fn forget_allocation_drop_remaining(&mut self) { - let remaining = self.as_raw_mut_slice(); - - // overwrite the individual fields instead of creating a new - // struct and then overwriting &mut self. - // this creates less assembly - self.cap = 0; - self.buf = unsafe { NonNull::new_unchecked(RawVec::NEW.ptr()) }; - self.ptr = self.buf.as_ptr(); - self.end = self.buf.as_ptr(); - - // Dropping the remaining elements can panic, so this needs to be - // done only after updating the other fields. - unsafe { - ptr::drop_in_place(remaining); - } - } - /// Forgets to Drop the remaining elements while still allowing the backing allocation to be freed. + #[cfg(rune_nightly)] pub(crate) fn forget_remaining_elements(&mut self) { // For th ZST case, it is crucial that we mutate `end` here, not `ptr`. // `ptr` must stay aligned, while `end` may be unaligned. diff --git a/crates/rune-alloc/src/vec/mod.rs b/crates/rune-alloc/src/vec/mod.rs index d581a9575..77a9f3acd 100644 --- a/crates/rune-alloc/src/vec/mod.rs +++ b/crates/rune-alloc/src/vec/mod.rs @@ -62,7 +62,7 @@ //! //! [`try_push`]: Vec::try_push -pub(crate) use self::drain::Drain; +pub use self::drain::Drain; mod drain; pub use self::into_iter::IntoIter; @@ -2171,8 +2171,10 @@ impl Vec { /// # Examples /// /// ``` + /// use rune::alloc::Vec; + /// /// // Allocate vector big enough for 10 elements. - /// let mut v = Vec::with_capacity(10); + /// let mut v = Vec::try_with_capacity(10)?; /// /// // Fill in the first 3 elements. /// let uninit = v.spare_capacity_mut(); @@ -2186,9 +2188,10 @@ impl Vec { /// } /// /// assert_eq!(&v, &[0, 1, 2]); + /// # Ok::<_, rune::alloc::Error>(()) /// ``` #[inline] - pub(crate) fn spare_capacity_mut(&mut self) -> &mut [MaybeUninit] { + pub fn spare_capacity_mut(&mut self) -> &mut [MaybeUninit] { // Note: // This method is not implemented in terms of `split_at_spare_mut`, // to prevent invalidation of pointers to the buffer. diff --git a/crates/rune-core/src/lib.rs b/crates/rune-core/src/lib.rs index 150fa969e..c8954efe1 100644 --- a/crates/rune-core/src/lib.rs +++ b/crates/rune-core/src/lib.rs @@ -24,6 +24,10 @@ //! dependency errors since its API is not stable. #![allow(clippy::module_inception)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_doc_tests)] +#![cfg_attr(rune_nightly, feature(rustdoc_missing_doc_code_examples))] +#![cfg_attr(rune_nightly, deny(rustdoc::missing_doc_code_examples))] #![no_std] #[cfg(feature = "std")] diff --git a/crates/rune/src/lib.rs b/crates/rune/src/lib.rs index e68eba714..9ae1cbaa7 100644 --- a/crates/rune/src/lib.rs +++ b/crates/rune/src/lib.rs @@ -129,6 +129,9 @@ #![no_std] #![deny(missing_docs)] #![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_doc_tests)] +#![cfg_attr(rune_nightly, feature(rustdoc_missing_doc_code_examples))] +#![cfg_attr(rune_nightly, deny(rustdoc::missing_doc_code_examples))] #![allow(clippy::enum_variant_names)] #![allow(clippy::needless_doctest_main)] #![allow(clippy::too_many_arguments)] diff --git a/crates/rune/src/modules/char.rs b/crates/rune/src/modules/char.rs index 123723b47..3b84aa0ff 100644 --- a/crates/rune/src/modules/char.rs +++ b/crates/rune/src/modules/char.rs @@ -245,7 +245,7 @@ fn is_uppercase(c: char) -> bool { /// /// Basic usage: /// -/// ``` +/// ```rune /// assert!(' '.is_whitespace()); /// /// // line break @@ -287,21 +287,21 @@ fn is_whitespace(c: char) -> bool { /// /// Basic usage: /// -/// ``` +/// ```rune /// assert_eq!('1'.to_digit(10), Some(1)); /// assert_eq!('f'.to_digit(16), Some(15)); /// ``` /// /// Passing a non-digit results in failure: /// -/// ``` +/// ```rune /// assert_eq!('f'.to_digit(10), None); /// assert_eq!('z'.to_digit(16), None); /// ``` /// /// Passing a large radix, causing a panic: /// -/// ```should_panic +/// ```rune,should_panic /// // this panics /// let _ = '1'.to_digit(37); /// ``` diff --git a/crates/rune/src/modules/f64.rs b/crates/rune/src/modules/f64.rs index 01ab77e04..25cf0685a 100644 --- a/crates/rune/src/modules/f64.rs +++ b/crates/rune/src/modules/f64.rs @@ -83,6 +83,8 @@ fn is_nan(this: f64) -> bool { /// Returns `true` if this value is positive infinity or negative infinity, and /// `false` otherwise. /// +/// # Examples +/// /// ```rune /// let f = 7.0f64; /// let inf = f64::INFINITY; @@ -102,6 +104,8 @@ fn is_infinite(this: f64) -> bool { /// Returns `true` if this number is neither infinite nor NaN. /// +/// # Examples +/// /// ```rune /// let f = 7.0f64; /// let inf = f64::INFINITY; @@ -121,7 +125,9 @@ fn is_finite(this: f64) -> bool { /// Returns `true` if the number is [subnormal]. /// -/// ``` +/// # Examples +/// +/// ```rune /// let min = f64::MIN_POSITIVE; // 2.2250738585072014e-308_f64 /// let max = f64::MAX; /// let lower_than_min = 1.0e-308_f64; @@ -136,6 +142,7 @@ fn is_finite(this: f64) -> bool { /// // Values between `0` and `min` are Subnormal. /// assert!(lower_than_min.is_subnormal()); /// ``` +/// /// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number #[rune::function(instance)] fn is_subnormal(this: f64) -> bool { @@ -144,6 +151,8 @@ fn is_subnormal(this: f64) -> bool { /// Returns `true` if the number is neither zero, infinite, [subnormal], or NaN. /// +/// # Examples +/// /// ```rune /// let min = f64::MIN_POSITIVE; // 2.2250738585072014e-308f64 /// let max = f64::MAX; @@ -173,6 +182,8 @@ fn is_normal(this: f64) -> bool { /// maxNum's problems with associativity. This also matches the behavior of /// libm’s fmax. /// +/// # Examples +/// /// ```rune /// let x = 1.0_f64; /// let y = 2.0_f64; @@ -192,6 +203,8 @@ fn max(this: f64, other: f64) -> f64 { /// minNum's problems with associativity. This also matches the behavior of /// libm’s fmin. /// +/// # Examples +/// /// ```rune /// let x = 1.0_f64; /// let y = 2.0_f64; @@ -207,7 +220,7 @@ fn min(this: f64, other: f64) -> f64 { /// /// # Examples /// -/// ``` +/// ```rune /// let x = 3.5_f64; /// let y = -3.5_f64; /// diff --git a/crates/rune/src/modules/iter.rs b/crates/rune/src/modules/iter.rs index 4be9fd5e2..d9d48654d 100644 --- a/crates/rune/src/modules/iter.rs +++ b/crates/rune/src/modules/iter.rs @@ -730,7 +730,7 @@ float_product_ops!(product_float, f64); /// /// Basic usage: /// -/// ``` +/// ```rune /// let a = [1, 2, 3]; /// /// // the sum of all of the elements of the array diff --git a/crates/rune/src/modules/string.rs b/crates/rune/src/modules/string.rs index 0a3ae245d..34481aa1f 100644 --- a/crates/rune/src/modules/string.rs +++ b/crates/rune/src/modules/string.rs @@ -189,7 +189,7 @@ fn string_from_str(value: &str) -> VmResult { /// /// Basic usage: /// -/// ``` +/// ```rune /// let s = String::new(); /// ``` #[rune::function(free, path = String::new)] @@ -372,7 +372,7 @@ fn clear(this: &mut String) { /// /// Basic usage: /// -/// ``` +/// ```rune /// let bananas = "bananas"; /// /// assert!(bananas.contains("nana")); @@ -894,7 +894,7 @@ fn is_empty(this: &str) -> bool { /// /// When the pattern doesn't match, it returns this string slice as [`String`]: /// -/// ``` +/// ```rune /// let s = "this is old"; /// assert_eq!(s, s.replace("cookie monster", "little lamb")); /// ``` diff --git a/crates/rune/src/runtime/budget.rs b/crates/rune/src/runtime/budget.rs index 6456711b6..d5240ade3 100644 --- a/crates/rune/src/runtime/budget.rs +++ b/crates/rune/src/runtime/budget.rs @@ -13,16 +13,18 @@ use core::future::Future; use core::pin::Pin; use core::task::{Context, Poll}; -use pin_project::pin_project; +use crate::alloc::callable::Callable; -#[cfg(feature = "std")] -#[cfg(not(feature = "std"))] -static BUDGET: Cell = Cell::new(usize::MAX); +use pin_project::pin_project; -/// Something being budgeted. +/// Wrapper for something being [budgeted]. +/// +/// See [with]. +/// +/// [budgeted]: self #[pin_project] pub struct Budget { - /// The current budget. + /// Instruction budget. budget: usize, /// The thing being budgeted. #[pin] @@ -38,24 +40,57 @@ pub struct Budget { /// native functions that you provide to Rune to ensure that the limits you /// impose cannot be circumvented. /// +/// The following things can be wrapped: +/// * A [`FnOnce`] closure, like `with(|| println!("Hello World")).call()`. +/// * A [`Future`], like `with(async { /* async work */ }).await`; +/// +/// It's also possible to wrap other wrappers which implement [`Callable`]. +/// +/// # Examples +/// /// ```no_run /// use rune::runtime::budget; /// use rune::Vm; /// /// let mut vm: Vm = todo!(); -/// // The virtual machine and any tasks associated with it is only allowed to execute 100 instructions. +/// // The virtual machine and any tasks associated with it is only allowed to execute 100 budget. /// budget::with(100, || vm.call(&["main"], ())).call()?; /// # Ok::<(), rune::support::Error>(()) /// ``` +/// +/// This budget can be conveniently combined with the memory [`limit`] module +/// due to both wrappers implementing [`Callable`]. +/// +/// [`limit`]: crate::alloc::limit +/// +/// ``` +/// use rune::runtime::budget; +/// use rune::alloc::{limit, Vec}; +/// +/// #[derive(Debug, PartialEq)] +/// struct Marker; +/// +/// // Limit the given closure to run one instruction and allocate 1024 bytes. +/// let f = budget::with(1, limit::with(1024, || { +/// assert!(budget::take()); +/// assert!(!budget::take()); +/// assert!(Vec::::try_with_capacity(1).is_ok()); +/// assert!(Vec::::try_with_capacity(1024).is_ok()); +/// assert!(Vec::::try_with_capacity(1025).is_err()); +/// Marker +/// })); +/// +/// assert_eq!(f.call(), Marker); +/// ``` pub fn with(budget: usize, value: T) -> Budget { tracing::trace!(?budget); Budget { budget, value } } -/// Take a ticket from the budget, indicating with `true` if the budget is -/// maintained +/// Take a ticket from the budget, returning `true` if we were still within the +/// budget before the ticket was taken, `false` otherwise. #[inline(never)] -pub(crate) fn take() -> bool { +pub fn take() -> bool { self::no_std::rune_budget_take() } @@ -68,14 +103,26 @@ impl Drop for BudgetGuard { } } -impl Budget +impl Budget where - T: FnOnce() -> O, + T: Callable, { - /// Call the wrapped function. - pub fn call(self) -> O { + /// Call the budgeted function. + pub fn call(self) -> T::Output { + Callable::call(self) + } +} + +impl Callable for Budget +where + T: Callable, +{ + type Output = T::Output; + + #[inline] + fn call(self) -> Self::Output { let _guard = BudgetGuard(self::no_std::rune_budget_replace(self.budget)); - (self.value)() + self.value.call() } } diff --git a/site/templates/macros.html b/site/templates/macros.html index 21f0023eb..4ab11fd3b 100644 --- a/site/templates/macros.html +++ b/site/templates/macros.html @@ -40,16 +40,16 @@ {% endif %}
  • - + - rustdocs + rust docs (git)
  • - + - runedocs + rune docs (git)