Skip to content

Commit e2d7ab3

Browse files
committed
Don't allocate for PyNativeFunc when it's just a fn pointer
1 parent c1c9631 commit e2d7ab3

File tree

7 files changed

+40
-14
lines changed

7 files changed

+40
-14
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

derive/src/pyclass.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ fn extract_impl_items(mut items: Vec<ItemSig>) -> Result<TokenStream2, Diagnosti
458458
let transform = if vec!["new", "call"].contains(&slot_ident.to_string().as_str()) {
459459
quote! { ::rustpython_vm::function::IntoPyNativeFunc::into_func }
460460
} else {
461-
quote! { Box::new }
461+
quote! { ::rustpython_vm::__exports::smallbox! }
462462
};
463463
let into_func = quote_spanned! {item_ident.span()=>
464464
#transform(Self::#item_ident)

vm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ is-macro = "0.1"
7272
result-like = "^0.2.1"
7373
foreign-types = "0.3"
7474
num_enum = "0.4"
75+
smallbox = "0.8"
7576

7677
flame = { version = "0.2", optional = true }
7778
flamer = { version = "0.3", optional = true }

vm/src/function.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::ops::RangeInclusive;
44

55
use indexmap::IndexMap;
66
use result_like::impl_option_like;
7+
use smallbox::{smallbox, space::S1, SmallBox};
78

89
use crate::exceptions::PyBaseExceptionRef;
910
use crate::obj::objtuple::PyTuple;
@@ -453,8 +454,11 @@ tuple_from_py_func_args!(A, B, C, D);
453454
tuple_from_py_func_args!(A, B, C, D, E);
454455
tuple_from_py_func_args!(A, B, C, D, E, F);
455456

457+
/// A container that can hold a `dyn Fn*` trait object, but doesn't allocate if it's only a fn() pointer
458+
pub type FunctionBox<T> = SmallBox<T, S1>;
459+
456460
/// A built-in Python function.
457-
pub type PyNativeFunc = Box<dyn Fn(&VirtualMachine, PyFuncArgs) -> PyResult + 'static>;
461+
pub type PyNativeFunc = FunctionBox<dyn Fn(&VirtualMachine, PyFuncArgs) -> PyResult + 'static>;
458462

459463
/// Implemented by types that are or can generate built-in functions.
460464
///
@@ -479,7 +483,7 @@ where
479483
F: Fn(&VirtualMachine, PyFuncArgs) -> PyResult + 'static,
480484
{
481485
fn into_func(self) -> PyNativeFunc {
482-
Box::new(self)
486+
smallbox!(self)
483487
}
484488
}
485489

@@ -499,7 +503,7 @@ macro_rules! into_py_native_func_tuple {
499503
R: IntoPyObject,
500504
{
501505
fn into_func(self) -> PyNativeFunc {
502-
Box::new(move |vm, args| {
506+
smallbox!(move |vm: &VirtualMachine, args: PyFuncArgs| {
503507
let ($($n,)*) = args.bind::<($($T,)*)>(vm)?;
504508

505509
(self)($($n,)* vm).into_pyobject(vm)
@@ -515,7 +519,7 @@ macro_rules! into_py_native_func_tuple {
515519
R: IntoPyObject,
516520
{
517521
fn into_func(self) -> PyNativeFunc {
518-
Box::new(move |vm, args| {
522+
smallbox!(move |vm: &VirtualMachine, args: PyFuncArgs| {
519523
let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
520524

521525
(self)(&zelf, $($n,)* vm).into_pyobject(vm)
@@ -597,3 +601,15 @@ pub fn single_or_tuple_any<T: PyValue, F: Fn(PyRef<T>) -> PyResult<bool>>(
597601
};
598602
checker.check(obj)
599603
}
604+
605+
#[cfg(test)]
606+
mod tests {
607+
#[test]
608+
fn test_functionbox_noalloc() {
609+
fn py_func(_b: bool, _vm: &crate::VirtualMachine) -> i32 {
610+
1
611+
}
612+
let f = super::IntoPyNativeFunc::into_func(py_func);
613+
assert!(!f.is_heap());
614+
}
615+
}

vm/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,5 @@ pub use rustpython_bytecode::*;
8888
#[doc(hidden)]
8989
pub mod __exports {
9090
pub use maplit::hashmap;
91+
pub use smallbox::smallbox;
9192
}

vm/src/obj/objgetset.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22
33
*/
44
use super::objtype::PyClassRef;
5-
use crate::function::{OptionalArg, OwnedParam, RefParam};
5+
use crate::function::{FunctionBox, OptionalArg, OwnedParam, RefParam};
66
use crate::pyobject::{
77
IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject,
88
TypeProtocol,
99
};
1010
use crate::slots::SlotDescriptor;
1111
use crate::vm::VirtualMachine;
1212

13-
pub type PyGetterFunc = Box<dyn Fn(&VirtualMachine, PyObjectRef) -> PyResult>;
14-
pub type PySetterFunc = Box<dyn Fn(&VirtualMachine, PyObjectRef, PyObjectRef) -> PyResult<()>>;
13+
pub type PyGetterFunc = FunctionBox<dyn Fn(&VirtualMachine, PyObjectRef) -> PyResult>;
14+
pub type PySetterFunc =
15+
FunctionBox<dyn Fn(&VirtualMachine, PyObjectRef, PyObjectRef) -> PyResult<()>>;
1516

1617
pub trait IntoPyGetterFunc<T> {
1718
fn into_getter(self) -> PyGetterFunc;
@@ -24,7 +25,7 @@ where
2425
R: IntoPyObject,
2526
{
2627
fn into_getter(self) -> PyGetterFunc {
27-
Box::new(move |vm, obj| {
28+
smallbox::smallbox!(move |vm: &VirtualMachine, obj| {
2829
let obj = T::try_from_object(vm, obj)?;
2930
(self)(obj, vm).into_pyobject(vm)
3031
})
@@ -38,7 +39,7 @@ where
3839
R: IntoPyObject,
3940
{
4041
fn into_getter(self) -> PyGetterFunc {
41-
Box::new(move |vm, obj| {
42+
smallbox::smallbox!(move |vm: &VirtualMachine, obj| {
4243
let zelf = PyRef::<S>::try_from_object(vm, obj)?;
4344
(self)(&zelf, vm).into_pyobject(vm)
4445
})
@@ -95,7 +96,7 @@ where
9596
R: IntoPyNoResult,
9697
{
9798
fn into_setter(self) -> PySetterFunc {
98-
Box::new(move |vm, obj, value| {
99+
smallbox::smallbox!(move |vm: &VirtualMachine, obj, value| {
99100
let obj = T::try_from_object(vm, obj)?;
100101
let value = V::try_from_object(vm, value)?;
101102
(self)(obj, value, vm).into_noresult()
@@ -111,7 +112,7 @@ where
111112
R: IntoPyNoResult,
112113
{
113114
fn into_setter(self) -> PySetterFunc {
114-
Box::new(move |vm, obj, value| {
115+
smallbox::smallbox!(move |vm: &VirtualMachine, obj, value| {
115116
let zelf = PyRef::<S>::try_from_object(vm, obj)?;
116117
let value = V::try_from_object(vm, value)?;
117118
(self)(&zelf, value, vm).into_noresult()

vm/src/slots.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::function::{OptionalArg, PyFuncArgs, PyNativeFunc};
1+
use crate::function::{FunctionBox, OptionalArg, PyFuncArgs, PyNativeFunc};
22
use crate::pyobject::{IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject};
33
use crate::VirtualMachine;
44

@@ -44,7 +44,7 @@ pub trait SlotCall: PyValue {
4444
fn call(&self, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult;
4545
}
4646

47-
pub type PyDescrGetFunc = Box<
47+
pub type PyDescrGetFunc = FunctionBox<
4848
dyn Fn(&VirtualMachine, PyObjectRef, Option<PyObjectRef>, OptionalArg<PyObjectRef>) -> PyResult,
4949
>;
5050

0 commit comments

Comments
 (0)