refactor object allocation system

This commit is contained in:
Nikolay Kim 2017-08-11 20:17:09 -07:00
parent bf793b4b71
commit 03b6e5c122
10 changed files with 305 additions and 92 deletions

View File

@ -269,9 +269,10 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
let py = gil.python();
// automatically initialize the class on-demand
_pyo3::typeob::initialize_type::<#cls>(py, None).expect(
format!("An error occurred while initializing class {}",
<#cls as _pyo3::typeob::PyTypeInfo>::NAME).as_ref());
_pyo3::typeob::initialize_type::<#cls>(py, None)
.map_err(|e| e.print(py))
.expect(format!("An error occurred while initializing class {}",
<#cls as _pyo3::typeob::PyTypeInfo>::NAME).as_ref());
}
});
}

View File

@ -73,7 +73,7 @@ impl<T> FreeList<T> {
impl<T> PyObjectAlloc<T> for T where T: PyObjectWithFreeList {
unsafe fn alloc(_py: Python, value: T) -> PyResult<*mut ffi::PyObject> {
unsafe fn alloc(_py: Python) -> PyResult<*mut ffi::PyObject> {
let obj = if let Some(obj) = <T as PyObjectWithFreeList>::get_free_list().pop() {
ffi::PyObject_Init(obj, <T as PyTypeInfo>::type_object());
obj
@ -81,54 +81,59 @@ impl<T> PyObjectAlloc<T> for T where T: PyObjectWithFreeList {
ffi::PyType_GenericAlloc(<T as PyTypeInfo>::type_object(), 0)
};
let ptr = (obj as *mut u8).offset(<T as PyTypeInfo>::OFFSET) as *mut T;
std::ptr::write(ptr, value);
Ok(obj)
}
#[cfg(Py_3)]
unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) {
let ptr = (obj as *mut u8).offset(<T as PyTypeInfo>::OFFSET) as *mut T;
std::ptr::drop_in_place(ptr);
unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject) {
Self::drop(py, obj);
if ffi::PyObject_CallFinalizerFromDealloc(obj) < 0 {
return
}
if let Some(obj) = <T as PyObjectWithFreeList>::get_free_list().insert(obj) {
let ty = ffi::Py_TYPE(obj);
if ffi::PyType_IS_GC(ty) != 0 {
ffi::PyObject_GC_Del(obj as *mut ::c_void);
} else {
ffi::PyObject_Free(obj as *mut ::c_void);
}
match (*T::type_object()).tp_free {
Some(free) => free(obj as *mut ::c_void),
None => {
let ty = ffi::Py_TYPE(obj);
if ffi::PyType_IS_GC(ty) != 0 {
ffi::PyObject_GC_Del(obj as *mut ::c_void);
} else {
ffi::PyObject_Free(obj as *mut ::c_void);
}
// For heap types, PyType_GenericAlloc calls INCREF on the type objects,
// so we need to call DECREF here:
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
// For heap types, PyType_GenericAlloc calls INCREF on the type objects,
// so we need to call DECREF here:
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}
}
}
}
#[cfg(not(Py_3))]
unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) {
let ptr = (obj as *mut u8).offset(<T as PyTypeInfo>::OFFSET) as *mut T;
std::ptr::drop_in_place(ptr);
unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject) {
Self::drop(py, obj);
if let Some(obj) = <T as PyObjectWithFreeList>::get_free_list().insert(obj) {
let ty = ffi::Py_TYPE(obj);
if ffi::PyType_IS_GC(ty) != 0 {
ffi::PyObject_GC_Del(obj as *mut ::c_void);
} else {
ffi::PyObject_Free(obj as *mut ::c_void);
}
match (*T::type_object()).tp_free {
Some(free) => free(obj as *mut ::c_void),
None => {
let ty = ffi::Py_TYPE(obj);
if ffi::PyType_IS_GC(ty) != 0 {
ffi::PyObject_GC_Del(obj as *mut ::c_void);
} else {
ffi::PyObject_Free(obj as *mut ::c_void);
}
// For heap types, PyType_GenericAlloc calls INCREF on the type objects,
// so we need to call DECREF here:
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
// For heap types, PyType_GenericAlloc calls INCREF on the type objects,
// so we need to call DECREF here:
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}
}
}
}

View File

@ -12,7 +12,7 @@ use objects::PyObjectRef;
use objectprotocol::ObjectProtocol;
use conversion::{ToPyObject, IntoPyObject, FromPyObject};
use python::{Python, IntoPyPointer, ToPyPointer};
use typeob::{PyTypeInfo, PyObjectAlloc};
use typeob::{PyTypeInfo, PyTypeObject};
pub struct PyToken(PhantomData<Rc<()>>);
@ -165,13 +165,13 @@ impl<T> Py<T> where T: PyTypeInfo,
/// Returns `Py<T>`.
pub fn new<F>(py: Python, f: F) -> PyResult<Py<T>>
where F: FnOnce(::PyToken) -> T,
T: PyObjectAlloc<T>
T: PyTypeObject + PyTypeInfo
{
let ob = f(PyToken(PhantomData));
let ob = <T as PyTypeObject>::create(py)?;
ob.init(f)?;
let ob = unsafe {
let ob = try!(<T as PyObjectAlloc<T>>::alloc(py, ob));
Py::from_owned_ptr(ob)
Py::from_owned_ptr(ob.into_ptr())
};
Ok(ob)
}
@ -180,13 +180,13 @@ impl<T> Py<T> where T: PyTypeInfo,
/// Returns references to `T`
pub fn new_ref<F>(py: Python, f: F) -> PyResult<&T>
where F: FnOnce(::PyToken) -> T,
T: PyObjectAlloc<T>
T: PyTypeObject + PyTypeInfo
{
let ob = f(PyToken(PhantomData));
let ob = <T as PyTypeObject>::create(py)?;
ob.init(f)?;
unsafe {
let ob = try!(<T as PyObjectAlloc<T>>::alloc(py, ob));
Ok(py.from_owned_ptr(ob))
Ok(py.from_owned_ptr(ob.into_ptr()))
}
}
@ -194,13 +194,13 @@ impl<T> Py<T> where T: PyTypeInfo,
/// Returns mutable references to `T`
pub fn new_mut<F>(py: Python, f: F) -> PyResult<&mut T>
where F: FnOnce(::PyToken) -> T,
T: PyObjectAlloc<T>
T: PyTypeObject + PyTypeInfo
{
let ob = f(PyToken(PhantomData));
let ob = <T as PyTypeObject>::create(py)?;
ob.init(f)?;
unsafe {
let ob = try!(<T as PyObjectAlloc<T>>::alloc(py, ob));
Ok(py.mut_from_owned_ptr(ob))
Ok(py.mut_from_owned_ptr(ob.into_ptr()))
}
}

View File

@ -6,7 +6,7 @@ use ffi;
use pythonrun;
use err::{PyErr, PyResult, PyDowncastError};
use instance::{AsPyRef, PyObjectWithToken};
use objects::PyObjectRef;
use objects::{PyTuple, PyObjectRef};
use conversion::{ToPyObject, ToBorrowedObject,
IntoPyObject, IntoPyTuple, FromPyObject, PyTryFrom};
use python::{Python, ToPyPointer, IntoPyPointer, IntoPyDictPointer};
@ -179,6 +179,33 @@ impl PyObject {
result
}
/// Calls the object without arguments.
/// This is equivalent to the Python expression: 'self()'
pub fn call0(&self, py: Python) -> PyResult<PyObject>
{
let args = PyTuple::empty(py).into_ptr();
let result = unsafe {
PyObject::from_owned_ptr_or_err(
py, ffi::PyObject_Call(self.as_ptr(), args, std::ptr::null_mut()))
};
py.xdecref(args);
result
}
/// Calls the object.
/// This is equivalent to the Python expression: 'self(*args)'
pub fn call1<A>(&self, py: Python, args: A) -> PyResult<PyObject>
where A: IntoPyTuple
{
let args = args.into_tuple(py).into_ptr();
let result = unsafe {
PyObject::from_owned_ptr_or_err(
py, ffi::PyObject_Call(self.as_ptr(), args, std::ptr::null_mut()))
};
py.xdecref(args);
result
}
/// Calls a method on the object.
/// This is equivalent to the Python expression: 'self.name(*args, **kwargs)'
pub fn call_method<A, K>(&self, py: Python,
@ -198,6 +225,37 @@ impl PyObject {
result
})
}
/// Calls a method on the object.
/// This is equivalent to the Python expression: 'self.name()'
pub fn call_method0(&self, py: Python, name: &str) -> PyResult<PyObject>
{
name.with_borrowed_ptr(py, |name| unsafe {
let args = PyTuple::empty(py).into_ptr();
let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name);
let result = PyObject::from_owned_ptr_or_err(
py, ffi::PyObject_Call(ptr, args, std::ptr::null_mut()));
ffi::Py_DECREF(ptr);
py.xdecref(args);
result
})
}
/// Calls a method on the object.
/// This is equivalent to the Python expression: 'self.name(*args)'
pub fn call_method1<A>(&self, py: Python, name: &str, args: A) -> PyResult<PyObject>
where A: IntoPyTuple
{
name.with_borrowed_ptr(py, |name| unsafe {
let args = args.into_tuple(py).into_ptr();
let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name);
let result = PyObject::from_owned_ptr_or_err(
py, ffi::PyObject_Call(ptr, args, std::ptr::null_mut()));
ffi::Py_DECREF(ptr);
py.xdecref(args);
result
})
}
}
impl AsPyRef<PyObjectRef> for PyObject {

View File

@ -150,6 +150,9 @@ pub trait ObjectProtocol {
/// Gets the Python base object for this object.
fn get_base(&self) -> &<Self as PyTypeInfo>::BaseType where Self: PyTypeInfo;
/// Gets the Python base object for this object.
fn get_mut_base(&self) -> &mut <Self as PyTypeInfo>::BaseType where Self: PyTypeInfo;
/// Casts the PyObject to a concrete Python object type.
fn cast_as<'a, D>(&'a self) -> Result<&'a D, <D as PyTryFrom>::Error>
where D: PyTryFrom<Error=PyDowncastError>,
@ -418,6 +421,11 @@ impl<T> ObjectProtocol for T where T: PyObjectWithToken + ToPyPointer {
unsafe { self.py().from_borrowed_ptr(self.as_ptr()) }
}
fn get_mut_base(&self) -> &mut <Self as PyTypeInfo>::BaseType where Self: PyTypeInfo
{
unsafe { self.py().mut_from_borrowed_ptr(self.as_ptr()) }
}
fn cast_as<'a, D>(&'a self) -> Result<&'a D, <D as PyTryFrom>::Error>
where D: PyTryFrom<Error=PyDowncastError>,
&'a PyObjectRef: std::convert::From<&'a Self>

View File

@ -105,7 +105,7 @@ mod test {
let err: PyErr = gaierror.into();
let d = PyDict::new(py);
d.set_item("socket", py.import("socket").unwrap()).unwrap();
d.set_item("socket", py.import("socket").map_err(|e| e.print(py)).unwrap()).unwrap();
d.set_item("exc", err).unwrap();
py.run("assert isinstance(exc, socket.gaierror)", None, Some(d)).unwrap();

View File

@ -88,6 +88,21 @@ impl PyModule {
self.getattr(name)?.call(args, kwargs)
}
/// Calls a function in the module.
/// This is equivalent to the Python expression: `getattr(module, name)()`
pub fn call0(&self, name: &str) -> PyResult<&PyObjectRef>
{
self.getattr(name)?.call0()
}
/// Calls a function in the module.
/// This is equivalent to the Python expression: `getattr(module, name)(*args)`
pub fn call1<A>(&self, name: &str, args: A) -> PyResult<&PyObjectRef>
where A: IntoPyTuple
{
self.getattr(name)?.call1(args)
}
/// Gets a member from the module.
/// This is equivalent to the Python expression: `getattr(module, name)`
pub fn get(&self, name: &str) -> PyResult<&PyObjectRef>

View File

@ -349,6 +349,20 @@ impl<'p> Python<'p> {
self.unchecked_downcast(p)
}
/// Register borrowed `ffi::PyObject` pointer in release pool.
/// Panics if the pointer is `null`.
/// do unchecked downcast to specific type.
pub unsafe fn mut_from_borrowed_ptr<T>(self, ptr: *mut ffi::PyObject) -> &'p mut T
where T: PyTypeInfo
{
if ptr.is_null() {
::err::panic_after_error();
} else {
let p = pythonrun::register_borrowed(self, ptr);
self.unchecked_mut_downcast(p)
}
}
/// Register borrowed `ffi::PyObject` pointer in release pool.
/// Returns `Err(PyErr)` if the pointer is `null`.
/// do unchecked downcast to specific type.
@ -377,16 +391,6 @@ impl<'p> Python<'p> {
}
}
/// Register borrowed `ffi::PyObject` pointer in release pool.
/// Panics if the pointer is `null`.
/// do unchecked downcast to specific `T`, returns mutable reference.
pub unsafe fn mut_from_borrowed_ptr<T>(self, ptr: *mut ffi::PyObject) -> &'p mut T
where T: PyTypeInfo
{
let p = pythonrun::register_borrowed(self, ptr);
self.unchecked_mut_downcast(p)
}
/// Release PyObject reference.
#[inline]
pub fn release<T>(self, ob: T) where T: IntoPyPointer {

View File

@ -121,6 +121,23 @@ impl PyRawObject {
}
}
#[must_use]
pub unsafe fn new_with_ptr(py: Python,
ptr: *mut ffi::PyObject,
tp_ptr: *mut ffi::PyTypeObject,
curr_ptr: *mut ffi::PyTypeObject) -> PyResult<PyRawObject> {
if !ptr.is_null() {
Ok(PyRawObject {
ptr: ptr,
tp_ptr: tp_ptr,
curr_ptr: curr_ptr,
initialized: 0,
})
} else {
PyErr::fetch(py).into()
}
}
/// Initialize memory using value.
/// `PyRawObject` is used by class `__new__` method.
/// ```
@ -184,67 +201,88 @@ impl PyObjectWithToken for PyRawObject {
pub trait PyObjectAlloc<T> {
/// Allocates a new object (usually by calling ty->tp_alloc),
/// and initializes it using value.
unsafe fn alloc(py: Python, value: T) -> PyResult<*mut ffi::PyObject>;
unsafe fn alloc(py: Python) -> PyResult<*mut ffi::PyObject>;
/// Calls the rust destructor for the object and frees the memory
/// (usually by calling ptr->ob_type->tp_free).
/// This function is used as tp_dealloc implementation.
unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject);
/// Calls the rust destructor for the object.
unsafe fn drop(_py: Python, _obj: *mut ffi::PyObject) {}
}
impl<T> PyObjectAlloc<T> for T where T : PyTypeInfo {
default unsafe fn alloc(_py: Python, value: T) -> PyResult<*mut ffi::PyObject> {
#[allow(unconditional_recursion)]
/// Calls the rust destructor for the object.
default unsafe fn drop(py: Python, obj: *mut ffi::PyObject) {
if T::OFFSET != 0 {
let ptr = (obj as *mut u8).offset(T::OFFSET) as *mut T;
std::ptr::drop_in_place(ptr);
T::BaseType::drop(py, obj);
}
}
default unsafe fn alloc(_py: Python) -> PyResult<*mut ffi::PyObject> {
// TODO: remove this
T::init_type();
let obj = ffi::PyType_GenericAlloc(T::type_object(), 0);
let ptr = (obj as *mut u8).offset(T::OFFSET) as *mut T;
std::ptr::write(ptr, value);
let tp_ptr = T::type_object();
let alloc = (*tp_ptr).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc);
let obj = alloc(tp_ptr, 0);
Ok(obj)
}
#[cfg(Py_3)]
default unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) {
let ptr = (obj as *mut u8).offset(T::OFFSET) as *mut T;
std::ptr::drop_in_place(ptr);
default unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject) {
Self::drop(py, obj);
if ffi::PyObject_CallFinalizerFromDealloc(obj) < 0 {
return
}
let ty = ffi::Py_TYPE(obj);
if ffi::PyType_IS_GC(ty) != 0 {
ffi::PyObject_GC_Del(obj as *mut ::c_void);
} else {
ffi::PyObject_Free(obj as *mut ::c_void);
}
match (*T::type_object()).tp_free {
Some(free) => free(obj as *mut ::c_void),
None => {
let ty = ffi::Py_TYPE(obj);
if ffi::PyType_IS_GC(ty) != 0 {
ffi::PyObject_GC_Del(obj as *mut ::c_void);
} else {
ffi::PyObject_Free(obj as *mut ::c_void);
}
// For heap types, PyType_GenericAlloc calls INCREF on the type objects,
// so we need to call DECREF here:
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
// For heap types, PyType_GenericAlloc calls INCREF on the type objects,
// so we need to call DECREF here:
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}
}
}
#[cfg(not(Py_3))]
default unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) {
let ptr = (obj as *mut u8).offset(T::OFFSET) as *mut T;
std::ptr::drop_in_place(ptr);
default unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject) {
Self::drop(py, obj);
let ty = ffi::Py_TYPE(obj);
if ffi::PyType_IS_GC(ty) != 0 {
ffi::PyObject_GC_Del(obj as *mut ::c_void);
} else {
ffi::PyObject_Free(obj as *mut ::c_void);
}
match (*T::type_object()).tp_free {
Some(free) => free(obj as *mut ::c_void),
None => {
let ty = ffi::Py_TYPE(obj);
if ffi::PyType_IS_GC(ty) != 0 {
ffi::PyObject_GC_Del(obj as *mut ::c_void);
} else {
ffi::PyObject_Free(obj as *mut ::c_void);
}
// For heap types, PyType_GenericAlloc calls INCREF on the type objects,
// so we need to call DECREF here:
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
// For heap types, PyType_GenericAlloc calls INCREF on the type objects,
// so we need to call DECREF here:
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}
}
}
}
@ -258,6 +296,21 @@ pub trait PyTypeObject {
/// Retrieves the type object for this Python object type.
fn type_object() -> Py<PyType>;
/// Create PyRawObject which can be initialized with rust value
#[must_use]
fn create(py: Python) -> PyResult<PyRawObject>
where Self: Sized + PyObjectAlloc<Self> + PyTypeInfo
{
<Self as PyTypeObject>::init_type();
unsafe {
let ptr = <Self as PyObjectAlloc<Self>>::alloc(py)?;
PyRawObject::new_with_ptr(
py, ptr,
<Self as PyTypeInfo>::type_object(),
<Self as PyTypeInfo>::type_object())
}
}
}
impl<T> PyTypeObject for T where T: PyObjectAlloc<T> + PyTypeInfo {

View File

@ -1307,7 +1307,7 @@ fn getter_setter_autogen() {
#[py::class]
struct BaseClass {
#[prop(get)]
val1: usize
val1: usize,
}
#[py::methods]
@ -1321,7 +1321,7 @@ impl BaseClass {
#[py::class(base=BaseClass)]
struct SubClass {
#[prop(get)]
val2: usize
val2: usize,
}
#[py::methods]
@ -1342,3 +1342,72 @@ fn inheritance_with_new_methods() {
let inst = typeobj.call(NoArgs, NoArgs).unwrap();
py_run!(py, inst, "assert inst.val1 == 10; assert inst.val2 == 5");
}
#[py::class]
struct BaseClassWithDrop {
token: PyToken,
data: Option<Arc<AtomicBool>>,
}
#[py::methods]
impl BaseClassWithDrop {
#[new]
fn __new__(obj: &PyRawObject) -> PyResult<()> {
obj.init(|t| BaseClassWithDrop{token: t, data: None})
}
}
impl Drop for BaseClassWithDrop {
fn drop(&mut self) {
if let Some(ref mut data) = self.data {
data.store(true, Ordering::Relaxed);
}
}
}
#[py::class(base=BaseClassWithDrop)]
struct SubClassWithDrop {
token: PyToken,
data: Option<Arc<AtomicBool>>,
}
#[py::methods]
impl SubClassWithDrop {
#[new]
fn __new__(obj: &PyRawObject) -> PyResult<()> {
obj.init(|t| SubClassWithDrop{token: t, data: None})?;
BaseClassWithDrop::__new__(obj)
}
}
impl Drop for SubClassWithDrop {
fn drop(&mut self) {
if let Some(ref mut data) = self.data {
data.store(true, Ordering::Relaxed);
}
}
}
#[test]
fn inheritance_with_new_methods_with_drop() {
let drop_called1 = Arc::new(AtomicBool::new(false));
let drop_called2 = Arc::new(AtomicBool::new(false));
{
let gil = Python::acquire_gil();
let py = gil.python();
let typebase = py.get_type::<BaseClassWithDrop>();
let typeobj = py.get_type::<SubClassWithDrop>();
let inst = typeobj.call(NoArgs, NoArgs).unwrap();
let mut obj = SubClassWithDrop::try_from_mut(inst).unwrap();
obj.data = Some(drop_called1.clone());
let mut base = obj.get_mut_base();
base.data = Some(drop_called2.clone());
}
assert!(drop_called1.load(Ordering::Relaxed));
assert!(drop_called2.load(Ordering::Relaxed));
}