diff --git a/Cargo.lock b/Cargo.lock index 0f0eced175..115af7e9c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1645,6 +1645,7 @@ dependencies = [ "sha-1", "sha2", "sha3", + "smallbox", "socket2", "statrs", "subprocess", @@ -1818,6 +1819,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +[[package]] +name = "smallbox" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa0eec6425219a55511a35afac416a2eecc62f5fa1bec14f9b97de0caa7daebe" + [[package]] name = "smallvec" version = "1.2.0" diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index b5894ce9b9..42194395a0 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -458,7 +458,7 @@ fn extract_impl_items(mut items: Vec) -> Result #transform(Self::#item_ident) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index eeaca1247b..6e7c7ca32f 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -72,6 +72,7 @@ is-macro = "0.1" result-like = "^0.2.1" foreign-types = "0.3" num_enum = "0.4" +smallbox = "0.8" flame = { version = "0.2", optional = true } flamer = { version = "0.3", optional = true } diff --git a/vm/src/function.rs b/vm/src/function.rs index 63c6b0d56b..6a667de36e 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -4,6 +4,7 @@ use std::ops::RangeInclusive; use indexmap::IndexMap; use result_like::impl_option_like; +use smallbox::{smallbox, space::S1, SmallBox}; use crate::exceptions::PyBaseExceptionRef; use crate::obj::objtuple::PyTuple; @@ -453,8 +454,11 @@ tuple_from_py_func_args!(A, B, C, D); tuple_from_py_func_args!(A, B, C, D, E); tuple_from_py_func_args!(A, B, C, D, E, F); +/// A container that can hold a `dyn Fn*` trait object, but doesn't allocate if it's only a fn() pointer +pub type FunctionBox = SmallBox; + /// A built-in Python function. -pub type PyNativeFunc = Box PyResult + 'static>; +pub type PyNativeFunc = FunctionBox PyResult + 'static>; /// Implemented by types that are or can generate built-in functions. /// @@ -479,7 +483,7 @@ where F: Fn(&VirtualMachine, PyFuncArgs) -> PyResult + 'static, { fn into_func(self) -> PyNativeFunc { - Box::new(self) + smallbox!(self) } } @@ -499,7 +503,7 @@ macro_rules! into_py_native_func_tuple { R: IntoPyObject, { fn into_func(self) -> PyNativeFunc { - Box::new(move |vm, args| { + smallbox!(move |vm: &VirtualMachine, args: PyFuncArgs| { let ($($n,)*) = args.bind::<($($T,)*)>(vm)?; (self)($($n,)* vm).into_pyobject(vm) @@ -515,7 +519,7 @@ macro_rules! into_py_native_func_tuple { R: IntoPyObject, { fn into_func(self) -> PyNativeFunc { - Box::new(move |vm, args| { + smallbox!(move |vm: &VirtualMachine, args: PyFuncArgs| { let (zelf, $($n,)*) = args.bind::<(PyRef, $($T,)*)>(vm)?; (self)(&zelf, $($n,)* vm).into_pyobject(vm) @@ -597,3 +601,15 @@ pub fn single_or_tuple_any) -> PyResult>( }; checker.check(obj) } + +#[cfg(test)] +mod tests { + #[test] + fn test_functionbox_noalloc() { + fn py_func(_b: bool, _vm: &crate::VirtualMachine) -> i32 { + 1 + } + let f = super::IntoPyNativeFunc::into_func(py_func); + assert!(!f.is_heap()); + } +} diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 6e791e264f..1dbf723bc3 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -88,4 +88,5 @@ pub use rustpython_bytecode::*; #[doc(hidden)] pub mod __exports { pub use maplit::hashmap; + pub use smallbox::smallbox; } diff --git a/vm/src/obj/objgetset.rs b/vm/src/obj/objgetset.rs index c67ea6a82c..4e36c30948 100644 --- a/vm/src/obj/objgetset.rs +++ b/vm/src/obj/objgetset.rs @@ -2,7 +2,7 @@ */ use super::objtype::PyClassRef; -use crate::function::{OptionalArg, OwnedParam, RefParam}; +use crate::function::{FunctionBox, OptionalArg, OwnedParam, RefParam}; use crate::pyobject::{ IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, @@ -10,8 +10,9 @@ use crate::pyobject::{ use crate::slots::SlotDescriptor; use crate::vm::VirtualMachine; -pub type PyGetterFunc = Box PyResult>; -pub type PySetterFunc = Box PyResult<()>>; +pub type PyGetterFunc = FunctionBox PyResult>; +pub type PySetterFunc = + FunctionBox PyResult<()>>; pub trait IntoPyGetterFunc { fn into_getter(self) -> PyGetterFunc; @@ -24,7 +25,7 @@ where R: IntoPyObject, { fn into_getter(self) -> PyGetterFunc { - Box::new(move |vm, obj| { + smallbox::smallbox!(move |vm: &VirtualMachine, obj| { let obj = T::try_from_object(vm, obj)?; (self)(obj, vm).into_pyobject(vm) }) @@ -38,7 +39,7 @@ where R: IntoPyObject, { fn into_getter(self) -> PyGetterFunc { - Box::new(move |vm, obj| { + smallbox::smallbox!(move |vm: &VirtualMachine, obj| { let zelf = PyRef::::try_from_object(vm, obj)?; (self)(&zelf, vm).into_pyobject(vm) }) @@ -95,7 +96,7 @@ where R: IntoPyNoResult, { fn into_setter(self) -> PySetterFunc { - Box::new(move |vm, obj, value| { + smallbox::smallbox!(move |vm: &VirtualMachine, obj, value| { let obj = T::try_from_object(vm, obj)?; let value = V::try_from_object(vm, value)?; (self)(obj, value, vm).into_noresult() @@ -111,7 +112,7 @@ where R: IntoPyNoResult, { fn into_setter(self) -> PySetterFunc { - Box::new(move |vm, obj, value| { + smallbox::smallbox!(move |vm: &VirtualMachine, obj, value| { let zelf = PyRef::::try_from_object(vm, obj)?; let value = V::try_from_object(vm, value)?; (self)(&zelf, value, vm).into_noresult() diff --git a/vm/src/slots.rs b/vm/src/slots.rs index b673a5d5ed..8c4753e0d2 100644 --- a/vm/src/slots.rs +++ b/vm/src/slots.rs @@ -1,4 +1,4 @@ -use crate::function::{OptionalArg, PyFuncArgs, PyNativeFunc}; +use crate::function::{FunctionBox, OptionalArg, PyFuncArgs, PyNativeFunc}; use crate::pyobject::{IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject}; use crate::VirtualMachine; @@ -44,7 +44,7 @@ pub trait SlotCall: PyValue { fn call(&self, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult; } -pub type PyDescrGetFunc = Box< +pub type PyDescrGetFunc = FunctionBox< dyn Fn(&VirtualMachine, PyObjectRef, Option, OptionalArg) -> PyResult, >;