Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions crates/capi/src/methodobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::util::{CStrExt, FfiPtrExt};
use core::ffi::{c_char, c_int};
use core::fmt::Debug;
use rustpython_vm::function::{FuncArgs, HeapMethodDef, ItemDoc, PosArgs, PyMethodFlags};
use rustpython_vm::types::c_slots::{kwargs_ptr, split_args};
use rustpython_vm::{AsObject, PyObjectRef, PyRef, PyResult, VirtualMachine};

define_py_check!(fn PyCFunction_Check, types.builtin_function_or_method_type);
Expand Down Expand Up @@ -197,16 +198,12 @@ unsafe fn call_function_with_keywords(
.as_ref()
.map(|obj| obj.as_object().as_raw().cast_mut())
.unwrap_or_default();
let arg_tuple = vm.ctx.new_tuple(args.args);
let kwargs = vm.ctx.new_dict();
for (k, v) in args.kwargs {
kwargs.set_item(&*k, v, vm)?;
}
let (arg_tuple, kwargs) = split_args(vm, args)?;
unsafe {
f(
slf_ptr,
arg_tuple.as_object().as_raw().cast_mut(),
kwargs.as_object().as_raw().cast_mut(),
kwargs_ptr(kwargs.as_ref()),
)
.assume_owned_or_err(vm)
}
Expand Down
24 changes: 18 additions & 6 deletions crates/capi/src/object/pytype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use core::ptr;
use rustpython_vm::builtins::{PyStr, PyType};
use rustpython_vm::function::{ItemDoc, PyMethodFlags};
use rustpython_vm::types::{
AtomicPyTypeFlags, PyTypeFlags, PyTypeSlots,
AtomicPyTypeFlags, CNewFunc, CSlotId, CSlots, PyTypeFlags, PyTypeSlots,
SlotAccessor::{TpBase, TpDealloc, TpDoc, TpGetset, TpMembers, TpMethods, TpNew, TpTraverse},
};
use rustpython_vm::{AsObject, Py, PyObject};
Expand Down Expand Up @@ -97,8 +97,15 @@ pub unsafe extern "C" fn PyType_GetFullyQualifiedName(ptr: *mut PyTypeObject) ->
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyType_GetSlot(_ty: *const PyTypeObject, _slot: c_int) -> *mut c_void {
ptr::null_mut()
pub unsafe extern "C" fn PyType_GetSlot(ty: *const PyTypeObject, slot: c_int) -> *mut c_void {
let ty = unsafe { &*ty };
match CSlotId::from_raw(slot) {
Some(CSlotId::TpNew) => ty.c_tp_new().map_or(ptr::null_mut(), |f| f as *mut c_void),
None => {
eprintln!("PyType_GetSlot: unknown slot {slot}");
ptr::null_mut()
}
}
}

#[unsafe(no_mangle)]
Expand All @@ -111,6 +118,7 @@ pub unsafe extern "C" fn PyType_FromSlots(slots: *const PySlot) -> *mut PyObject
let attrs = Default::default();
let mut getsets = Vec::new();
let mut members = Vec::new();
let c_slots = CSlots::new();
let mut metaclass = vm.ctx.types.type_type.to_owned();

for slot in PySlot::iter(slots) {
Expand Down Expand Up @@ -166,7 +174,12 @@ pub unsafe extern "C" fn PyType_FromSlots(slots: *const PySlot) -> *mut PyObject
methods.push((name, method));
}
}
PySlotType::TypeSlot(TpNew | TpDealloc | TpTraverse | _, _) => {
PySlotType::TypeSlot(TpNew, pfunc) => unsafe {
c_slots
.new
.store(Some(core::mem::transmute::<*mut c_void, CNewFunc>(pfunc)));
Comment thread
bschoenmaeckers marked this conversation as resolved.
},
Comment thread
bschoenmaeckers marked this conversation as resolved.
PySlotType::TypeSlot(TpDealloc | TpTraverse | _, _) => {
// TODO
}
}
Expand Down Expand Up @@ -194,6 +207,7 @@ pub unsafe extern "C" fn PyType_FromSlots(slots: *const PySlot) -> *mut PyObject
|msg| vm.new_system_error(format!("Failed to create type from slots: {msg}")),
)?;

class.set_c_slots(c_slots, vm)?;
Comment thread
bschoenmaeckers marked this conversation as resolved.
let attrs = &class.attributes;
let class_static = unsafe { &*((&*class) as *const _) };
for (name, method) in methods {
Expand Down Expand Up @@ -425,7 +439,6 @@ mod tests {
}

#[test]
#[ignore]
fn zero_sized_class() {
#[pyclass(frozen)]
struct MyEmptyClass {}
Expand Down Expand Up @@ -484,7 +497,6 @@ mod tests {
}

#[test]
#[ignore]
fn subclass_with_property() {
#[pyclass(frozen, extends=PyList)]
struct MyList {}
Expand Down
23 changes: 23 additions & 0 deletions crates/derive-impl/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,28 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
sig_constructor,
sig_structseq,
);
// The table names `Self`, so it has to be an associated const on the
// payload, not a `static` item inside `extend_slots`.
let static_c_slots_impl = if sig_constructor {
Comment thread
bschoenmaeckers marked this conversation as resolved.
quote! {
impl ::rustpython_vm::types::HasStaticCSlots for #payload_ty {
const TABLE: ::rustpython_vm::types::StaticCSlots =
::rustpython_vm::types::StaticCSlots {
new: Some(::rustpython_vm::types::CSlotPair {
rust: <#payload_ty as ::rustpython_vm::types::Constructor>::slot_new as _,
c: ::rustpython_vm::types::c_new_for::<
::rustpython_vm::types::ViaConstructor<#payload_ty>,
> as _,
}),
};
}
}
} else {
quote! {}
};
quote! {
#imp
#static_c_slots_impl
impl ::rustpython_vm::class::PyClassImpl for #payload_ty {
const TP_FLAGS: ::rustpython_vm::types::PyTypeFlags = #flags;

Expand Down Expand Up @@ -2567,6 +2587,9 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac
} else if path.is_ident("Constructor") {
quote_spanned! { item_span =>
slots.new.store(Some(<Self as ::rustpython_vm::types::Constructor>::slot_new as _));
slots.static_c_slots.store(Some(
&<Self as ::rustpython_vm::types::HasStaticCSlots>::TABLE,
));
}
} else {
quote_spanned! { item_span =>
Expand Down
85 changes: 69 additions & 16 deletions crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ use crate::{
object::{Traverse, TraverseFn},
protocol::{PyIterReturn, PyNumberMethods},
types::{
AsNumber, Callable, Constructor, GetAttr, Initializer, NewFunc, PyTypeFlags, PyTypeSlots,
Representable, SLOT_DEFS, SetAttr, TypeDataRef, TypeDataRefMut, TypeDataSlot,
VectorCallFunc, fn_addr, new_wrapper,
AsNumber, CSlots, Callable, Constructor, GetAttr, Initializer, OwnedCSlots, PyTypeFlags,
PyTypeSlots, Representable, SLOT_DEFS, SetAttr, TypeDataRef, TypeDataRefMut, TypeDataSlot,
VectorCallFunc,
},
};
use core::{
Expand Down Expand Up @@ -352,6 +352,9 @@ pub struct HeapTypeExt {
pub qualname: PyRwLock<PyStrRef>,
pub slots: Option<PyRef<PyTuple<PyStrRef>>>,
pub type_data: PyRwLock<Option<TypeDataSlot>>,
/// The C slot table this type owns, once an extension has filled one in.
/// Types that inherit from it point at this same table.
pub c_slots: PyRwLock<Option<OwnedCSlots>>,
pub specialization_cache: TypeSpecializationCache,
/// The interpreter this type was created in, or `None` for the types the
/// shared context builds before any interpreter exists.
Expand Down Expand Up @@ -852,6 +855,7 @@ impl PyType {
qualname: PyRwLock::new(name),
slots: None,
type_data: PyRwLock::new(None),
c_slots: PyRwLock::new(None),
specialization_cache: TypeSpecializationCache::new(),
interpreter_id: HeapTypeExt::creating_interpreter_id(),
};
Expand Down Expand Up @@ -1313,6 +1317,7 @@ impl PyType {

if slots.flags.has_feature(PyTypeFlags::DISALLOW_INSTANTIATION) {
slots.new.store(None);
slots.c_slots.store(None);
} else if slots.new.load().is_none() {
let inherited = base.and_then(|base| base.slots.new.load());
if !is_heap
Expand All @@ -1327,6 +1332,9 @@ impl PyType {
);
}
slots.new.store(inherited);
slots
.c_slots
.store(base.and_then(|base| base.slots.c_slots.load()));
}
}

Expand Down Expand Up @@ -1646,8 +1654,44 @@ impl PyType {
attributes
}

/// Give this type a C tp_new, taking ownership of the table it is read
/// from and installing the trampoline that reaches it.
///
/// Errors if `self` is not a heap type, which is the only kind that can own
/// a table, or if it already owns one.
pub fn set_c_slots(&self, c_slots: CSlots, vm: &VirtualMachine) -> PyResult<()> {
let ext = self.heaptype_ext.as_ref().ok_or_else(|| {
vm.new_type_error(format!(
"cannot set C slots on static type '{}'",
self.name()
))
})?;
if c_slots.new.load().is_none()
&& self
.slots
.new
.load()
.is_some_and(crate::types::c_slots::is_c_trampoline)
&& let Some(inherited) = self.slots.c_slots().and_then(|slots| slots.new.load())
{
c_slots.new.store(Some(inherited));
}
let mut owned = ext.c_slots.write();
if owned.is_some() {
return Err(vm.new_type_error(format!("type '{}' already has C slots", self.name())));
}
let has_new = c_slots.new.load().is_some();
let table = OwnedCSlots::new(c_slots);
self.slots.c_slots.store(Some(table.as_ptr()));
if has_new {
self.slots.new.store(Some(crate::types::c_new_trampoline));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
*owned = Some(table);
Ok(())
}

// bound method for every type
pub(crate) fn __new__(zelf: PyRef<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
pub fn __new__(zelf: PyRef<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
let (subtype, args): (PyRef<Self>, FuncArgs) = args.bind(vm)?;
if !subtype.fast_issubclass(&zelf) {
return Err(vm.new_type_error(format!(
Expand Down Expand Up @@ -1967,11 +2011,11 @@ impl Constructor for PyType {
// Search the bases for the proper metatype to deal with this:
let winner = calculate_meta_class(metatype.clone(), bases.as_slice(), vm)?;
let metatype = if !winner.is(&metatype) {
let slot_new = winner.slots.new.load().ok_or_else(|| {
vm.new_type_error(format!("cannot create '{}' instances", winner.slot_name()))
})?;
// Pass it to the winner.
return slot_new(winner, args, vm);
if let Some(ref slot_new) = winner.slots.new.load() {
// Pass it to the winner
return slot_new(winner, args, vm);
}
winner
} else {
metatype
};
Expand Down Expand Up @@ -2243,6 +2287,7 @@ impl Constructor for PyType {
qualname: PyRwLock::new(qualname),
slots: heaptype_slots.clone(),
type_data: PyRwLock::new(None),
c_slots: PyRwLock::new(None),
specialization_cache: TypeSpecializationCache::new(),
interpreter_id: HeapTypeExt::creating_interpreter_id(),
};
Expand Down Expand Up @@ -3600,25 +3645,33 @@ pub(crate) fn call_slot_new(
return Err(vm.new_type_error(format!("cannot create '{}' instances", subtype.slot_name())));
}

// "is not safe" check (tp_new_wrapper). Walk past bases whose tp_new is
// new_wrapper so a Python subclass of a native heap type still reaches
// that type's tp_new; reject object.__new__(dict) and similar.
// "is not safe" check (tp_new_wrapper logic)
// Check that the user doesn't do something silly and unsafe like
// object.__new__(dict). To do this, we check that the most derived base
// that owns a real tp_new is this type. Walking past the types whose slot
// is only the dynamic wrapper, rather than past every heap type, keeps a
// heap type that carries its own tp_new, as one built from a C spec does,
// from being skipped.
let mut staticbase = subtype.clone();
while staticbase
.slots
.new
.load()
.is_some_and(|f| fn_addr(f) == fn_addr(new_wrapper as NewFunc))
.map(|f| crate::types::fn_addr(f))
== Some(crate::types::fn_addr(
crate::types::new_wrapper as crate::types::NewFunc,
))
{
match staticbase.base.load_owned() {
Some(base) => staticbase = base,
None => break,
}
}

let typ_new = typ.slots.new.load();
let staticbase_new = staticbase.slots.new.load();
if typ_new.map(fn_addr) != staticbase_new.map(fn_addr) {
// Check if staticbase's tp_new differs from typ's tp_new. Compare what the
// slot dispatches to, so that two types reached through the same C
// trampoline are still told apart by their C functions.
if typ.slots.new_identity() != staticbase.slots.new_identity() {
return Err(vm.new_type_error(format!(
"{}.__new__({}) is not safe, use {}.__new__()",
typ.slot_name(),
Expand Down
1 change: 0 additions & 1 deletion crates/vm/src/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
//! - Rust `Debug` formatting now consistently uses the allocation's vtable rather
//! than the statically selected payload's formatter. This is a Rust diagnostic
//! difference, not a Python semantic assertion.

mod core;
mod ext;
mod payload;
Expand Down
Loading
Loading