Expose more attributes as member descriptors (#8857)

* Read type.__module__ from the dict only for heap types

A static type's `__module__` is the text before the last `.` of its name,
or `builtins`. A heap type reads its dict and raises `AttributeError`
when the entry is missing; `type` and `object` reprs ignore that error.
`type()` no longer stores `__module__ = None` when the caller's globals
have no `__name__`.

`#[pyexception(module = ...)]` now reaches the type name, and static
types that relied on a dict `__module__` (ast nodes, pyexpat, ssl,
_hashlib and _interpchannels errors, ExceptionGroup) carry their module.

Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5

* Expose more builtin attributes as member descriptors

`code.co_flags`, `co_nlocals`, `co_filename`, `super.__self__`,
`__self_class__`, `__thisclass__`, `traceback.tb_frame`, `tb_lasti`,
`type.__itemsize__` and `builtin_function_or_method.__module__` are now
`#[pymember]` fields instead of getsets. `super` keeps its three
references in atomic cells instead of a lock, `tb_lasti` is an `i32`,
`co_nlocals` is stored at construction, and the builtin function
`__module__` is a writable object cell.

Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5

* Expose stdlib attributes as member descriptors

Decompressor `eof`/`needs_input`/`unused_data` (bz2, lzma, zlib, plus
lzma `check`), `ContextVar.name`, `functools.partial` `func`/`args`/
`keywords`, socket `family`/`type`/`proto`, `select.kevent` fields,
`SemLock` `handle`/`kind`/`maxvalue`/`name`, `Pickler.bin`/`fast`,
`xmlparser.intern`, ElementTree `XMLParser.entity`/`target`,
`UCD.unidata_version`, `_ExceptHookArgs` fields, multibytecodec
`stream`, and `_io` `Buffered*.raw` and `TextIOWrapper`
`buffer`/`encoding`/`line_buffering`/`write_through` are now
`#[pymember]` fields instead of getsets.

Values that changed under a lock move to atomic cells on the payload.
New member kinds `Short`, `Long`, `String`, `UShort`, `ULong`,
`LongLong` and `ULongLong` access exactly the width of their C type.

Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5

* Match bare super and static type itemsize

`super.__new__(super)` leaves `__thisclass__`, `__self__` and
`__self_class__` empty, and a super with no type takes the
zero-argument path when bound.

Static types carry their `tp_itemsize` (`type`, `memoryview`, `code`,
`frame`, generators, `_sre` objects, `_ctypes` thunks), inherit it from
their direct base when they leave it at 0, and a variable-size base
rejects any nonempty `__slots__` and a weakref slot.
`_thread._ExceptHookArgs` is a struct sequence.

Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5

* Expose SimpleNamespace.__dict__, __basicsize__ and __flags__ as members

`SimpleNamespace.__dict__` addresses the instance dict cell the same way
`module.__dict__` does. `slots.basicsize` now holds the full
`tp_basicsize` (header plus payload), so `__basicsize__` reads it
directly. Type flags are one atomic word: `__abstractmethods__` and
`_abc` set their bits in it in place, the separate `abc_tpflags` word is
gone, and `__flags__` reads the word as an unsigned long member.

Assisted-by: Grok:4.7
Assisted-by: Claude:claude-opus-5-5
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-09-28 00:03:27 +09:00
1 parent f87170772e
commit 31963fa767
58 files changed
+2010 -1251

No files matched your search

+2 -1
View File
@@ -130,7 +130,8 @@ impl PyMemberDef {
let mut flags = self.flags;
if flags & PY_RELATIVE_OFFSET != 0 {
// type creation adds tp_basicsize and clears the flag before GetOne.
offset += (rustpython_vm::object::SIZEOF_PYOBJECT_HEAD + ty.slots.basicsize) as isize;
// `slots.basicsize` is already the full tp_basicsize.
offset += ty.slots.basicsize as isize;
flags &= !PY_RELATIVE_OFFSET;
}
+1 -1
View File
@@ -59,7 +59,7 @@ pub unsafe extern "C" fn PyType_GetFullyQualifiedName(ptr: *mut PyTypeObject) ->
with_vm(|vm| {
let ty = unsafe { ptr.assume_borrowed() };
let qualname = ty.__qualname__(vm).try_downcast::<PyStr>(vm)?;
let module = ty.__module__(vm);
let module = ty.__module__(vm)?;
if let Some(module) = module.downcast_ref::<PyStr>()
&& module.as_wtf8() != "builtins"
+5
View File
@@ -520,6 +520,11 @@ bitflags! {
}
}
const _: () = {
assert!(core::mem::size_of::<CodeFlags>() == core::mem::size_of::<i32>());
assert!(core::mem::align_of::<CodeFlags>() == core::mem::align_of::<i32>());
};
impl CodeFlags {
/// The `__future__` flags that `compile()` accepts and that a compiled code
/// object inherits from its caller. Mirrors `PyCF_MASK`.
+11 -4
View File
@@ -551,12 +551,14 @@ fn generate_class_def(
}
});
// If repr(transparent) with a base, the type has the same memory layout as base,
// so basicsize should be 0 (no additional space beyond the base type)
// Otherwise, basicsize = sizeof(payload). The header size is added in __basicsize__ getter.
// so basicsize stays 0 and type creation copies the base's full tp_basicsize.
// Otherwise, basicsize is the object header plus the payload.
let basicsize = if is_repr_transparent && base.is_some() {
quote!(0)
} else {
quote!(std::mem::size_of::<#ident>())
quote!(
::rustpython_vm::object::SIZEOF_PYOBJECT_HEAD + ::core::mem::size_of::<#ident>()
)
};
if base.is_some() && is_pystruct {
bail_span!(ident, "PyStructSequence cannot have `base` class attr",);
@@ -914,8 +916,13 @@ pub(crate) fn impl_pyexception(attr: PunctuatedNestedMeta, item: &Item) -> Resul
None => quote! {},
};
let module_attr = match class_meta.optional_module()? {
Some(module) => quote! { module = #module, },
None => quote! { module = false, },
};
let ret = quote! {
#[pyclass(module = false, name = #class_name, base = #base_class_name #traverse_attr #payload_attr)]
#[pyclass(#module_attr name = #class_name, base = #base_class_name #traverse_attr #payload_attr)]
#item
#impl_pyclass
};
+9
View File
@@ -534,6 +534,15 @@ impl ExceptionItemMeta {
pub(crate) fn has_impl(&self) -> Result<bool> {
self.inner()._bool("impl")
}
/// `module = "..."` when present. Omitted means a builtin exception.
pub(crate) fn optional_module(&self) -> Result<Option<String>> {
if self.inner().meta_map.contains_key("module") {
self.module()
} else {
Ok(None)
}
}
}
impl core::ops::Deref for ExceptionItemMeta {
+2
View File
@@ -18,6 +18,7 @@ use libc::sem_t;
use nix::errno::Errno;
#[cfg(unix)]
#[repr(transparent)]
#[derive(Debug)]
pub struct SemHandle {
raw: *mut sem_t,
@@ -118,6 +119,7 @@ pub type RawSocket = SOCKET;
pub const INFINITE_TIMEOUT: u32 = INFINITE;
#[cfg(windows)]
#[repr(transparent)]
#[derive(Debug)]
pub struct SemHandle {
raw: HANDLE,
+27 -29
View File
@@ -6,14 +6,15 @@ pub(crate) use _bz2::module_def;
mod _bz2 {
use crate::compression::DecompressorArgs;
use crate::vm::{
Py, VirtualMachine,
builtins::{PyBaseExceptionRef, PyBytesRef, PyType},
Py, PyObject, VirtualMachine,
builtins::{PyBaseExceptionRef, PyBytes, PyType},
common::lock::PyMutex,
function::ArgBytesLike,
object::PyResult,
object::{PyAtomicRef, PyResult},
types::Constructor,
};
use alloc::fmt;
use core::sync::atomic::{AtomicBool, Ordering};
use rustpython_common::compression::bz2 as backend;
fn map_bz2_error(error: backend::Bz2Error, vm: &VirtualMachine) -> PyBaseExceptionRef {
@@ -31,15 +32,6 @@ mod _bz2 {
struct PyBZ2DecompressorInner {
decompress: backend::Decompressor,
unused_data: PyBytesRef,
}
impl PyBZ2DecompressorInner {
fn sync_visible_state(&mut self, vm: &VirtualMachine) {
if self.unused_data.as_bytes() != self.decompress.unused_data() {
self.unused_data = vm.ctx.new_bytes(self.decompress.unused_data().to_vec());
}
}
}
#[pyattr]
@@ -48,6 +40,14 @@ mod _bz2 {
struct BZ2Decompressor {
#[pytraverse(skip)]
inner: PyMutex<PyBZ2DecompressorInner>,
#[pymember]
#[pytraverse(skip)]
eof: AtomicBool,
#[pymember]
#[pytraverse(skip)]
needs_input: AtomicBool,
#[pymember(type = "object_ex")]
unused_data: PyAtomicRef<Option<PyObject>>,
}
impl fmt::Debug for BZ2Decompressor {
@@ -63,8 +63,10 @@ mod _bz2 {
Ok(Self {
inner: PyMutex::new(PyBZ2DecompressorInner {
decompress: backend::Decompressor::new(),
unused_data: vm.ctx.empty_bytes.clone(),
}),
eof: AtomicBool::new(false),
needs_input: AtomicBool::new(true),
unused_data: PyAtomicRef::from(Some(vm.ctx.empty_bytes.clone().into())),
})
}
}
@@ -84,25 +86,21 @@ mod _bz2 {
return Err(vm.new_value_error("Decompressor is unusable after a previous error"));
}
let result = inner.decompress.decompress(data, max_length);
inner.sync_visible_state(vm);
self.eof.store(inner.decompress.eof(), Ordering::Relaxed);
self.needs_input
.store(inner.decompress.needs_input(), Ordering::Relaxed);
let stale = self.unused_data.deref().is_none_or(|obj| {
obj.downcast_ref::<PyBytes>()
.is_none_or(|bytes| bytes.as_bytes() != inner.decompress.unused_data())
});
if stale {
let bytes = vm.ctx.new_bytes(inner.decompress.unused_data().to_vec());
// The previous bytes object is dropped after the slot is replaced.
let _previous = unsafe { self.unused_data.swap(Some(bytes.into())) };
}
result.map_err(|error| map_bz2_error(error, vm))
}
#[pygetset]
fn eof(&self) -> bool {
self.inner.lock().decompress.eof()
}
#[pygetset]
fn unused_data(&self) -> PyBytesRef {
self.inner.lock().unused_data.clone()
}
#[pygetset]
fn needs_input(&self) -> bool {
self.inner.lock().decompress.needs_input()
}
#[pymethod(name = "__reduce__")]
fn reduce(&self, vm: &VirtualMachine) -> PyResult<()> {
Err(vm.new_type_error("cannot pickle '_bz2.BZ2Decompressor' object"))
+2 -10
View File
@@ -974,6 +974,7 @@ mod _multibytecodec {
struct MultibyteStreamReader {
#[pytraverse(skip)]
codec: CodecRef,
#[pymember]
stream: PyObjectRef,
#[pytraverse(skip)]
inner: PyMutex<DecoderState>,
@@ -1006,11 +1007,6 @@ mod _multibytecodec {
#[pyclass(with(Constructor, Initializer), flags(BASETYPE))]
impl MultibyteStreamReader {
#[pygetset]
fn stream(&self) -> PyObjectRef {
self.stream.clone()
}
#[pygetset]
fn errors(&self, vm: &VirtualMachine) -> PyStrRef {
self.inner.lock().errors.name(vm)
@@ -1134,6 +1130,7 @@ mod _multibytecodec {
struct MultibyteStreamWriter {
#[pytraverse(skip)]
codec: CodecRef,
#[pymember]
stream: PyObjectRef,
#[pytraverse(skip)]
inner: PyMutex<EncoderState>,
@@ -1166,11 +1163,6 @@ mod _multibytecodec {
#[pyclass(with(Constructor, Initializer), flags(BASETYPE))]
impl MultibyteStreamWriter {
#[pygetset]
fn stream(&self) -> PyObjectRef {
self.stream.clone()
}
#[pygetset]
fn errors(&self, vm: &VirtualMachine) -> PyStrRef {
self.inner.lock().errors.name(vm)
+1 -5
View File
@@ -359,6 +359,7 @@ mod _contextvars {
#[pyclass(name, traverse)]
#[derive(PyPayload)]
struct ContextVar {
#[pymember]
name: PyStrRef,
default: Option<PyObjectRef>,
#[pytraverse(skip)]
@@ -436,11 +437,6 @@ mod _contextvars {
#[pyclass(with(Constructor, Hashable, Representable))]
impl ContextVar {
#[pygetset]
fn name(&self) -> PyStrRef {
self.name.clone()
}
#[pymethod]
fn get(
zelf: &Py<Self>,
+14 -27
View File
@@ -1829,11 +1829,9 @@ pub(crate) mod _elementtree {
/// The `pyexpat.xmlparser` doing the actual scanning, or `None`
/// before `__init__` (and after `close()` dropped it).
parser: Option<PyObjectRef>,
target: Option<PyObjectRef>,
/// The target again, when it is exactly our own `TreeBuilder`, so a
/// per-event dispatch is one clone rather than a type check.
native_target: Option<PyRef<PyTreeBuilder>>,
entity: Option<PyDictRef>,
/// Cache of raw expat names to their `{uri}local` form.
names: Option<PyDictRef>,
handle_start: Option<PyObjectRef>,
@@ -1855,6 +1853,10 @@ pub(crate) mod _elementtree {
)]
#[derive(Debug, PyPayload)]
pub(crate) struct PyXMLParser {
#[pymember]
entity: crate::vm::object::PyAtomicRef<Option<PyObject>>,
#[pymember]
target: crate::vm::object::PyAtomicRef<Option<PyObject>>,
state: PyRwLock<XMLParserState>,
}
@@ -1865,9 +1867,9 @@ pub(crate) mod _elementtree {
return;
};
st.parser.traverse(traverse_fn);
st.target.traverse(traverse_fn);
st.native_target.traverse(traverse_fn);
st.entity.traverse(traverse_fn);
self.entity.traverse(traverse_fn);
self.target.traverse(traverse_fn);
st.names.traverse(traverse_fn);
st.handle_start.traverse(traverse_fn);
st.handle_end.traverse(traverse_fn);
@@ -1887,9 +1889,9 @@ pub(crate) mod _elementtree {
out.extend(
[
st.parser.take(),
st.target.take(),
unsafe { self.entity.swap(None) },
unsafe { self.target.swap(None) },
st.native_target.take().map(Into::into),
st.entity.take().map(Into::into),
st.names.take().map(Into::into),
st.handle_start.take(),
st.handle_end.take(),
@@ -1920,6 +1922,8 @@ pub(crate) mod _elementtree {
impl Default for PyXMLParser {
fn default() -> Self {
Self {
entity: crate::vm::object::PyAtomicRef::from(None),
target: crate::vm::object::PyAtomicRef::from(None),
state: PyRwLock::new(XMLParserState::default()),
}
}
@@ -1963,9 +1967,11 @@ pub(crate) mod _elementtree {
_ => PyTreeBuilder::default().into_ref(&vm.ctx).into(),
};
let entity = vm.ctx.new_dict();
let _previous = unsafe { zelf.entity.swap(Some(entity.into())) };
let _previous = unsafe { zelf.target.swap(Some(target.clone())) };
let handlers = XMLParserState {
parser: Some(parser.clone()),
entity: Some(vm.ctx.new_dict()),
names: Some(vm.ctx.new_dict()),
handle_start_ns: optional_handler(&target, "start_ns", vm)?,
handle_end_ns: optional_handler(&target, "end_ns", vm)?,
@@ -1981,7 +1987,6 @@ pub(crate) mod _elementtree {
.is(PyTreeBuilder::class(&vm.ctx))
.then(|| target.clone().downcast::<PyTreeBuilder>().ok())
.flatten(),
target: Some(target),
};
let has_comment = handlers.handle_comment.is_some();
let has_pi = handlers.handle_pi.is_some();
@@ -2044,7 +2049,7 @@ pub(crate) mod _elementtree {
impl PyXMLParser {
fn check(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
let st = self.state.read();
if st.target.is_none() {
if self.target.deref().is_none() {
return Err(vm.new_value_error("XMLParser.__init__() wasn't called"));
}
st.parser
@@ -2125,24 +2130,6 @@ pub(crate) mod _elementtree {
#[pyclass(with(Constructor, Initializer), flags(BASETYPE, HAS_WEAKREF))]
impl PyXMLParser {
#[pygetset]
fn entity(&self, vm: &VirtualMachine) -> PyObjectRef {
self.state
.read()
.entity
.clone()
.map_or_else(|| vm.ctx.none(), Into::into)
}
#[pygetset]
fn target(&self, vm: &VirtualMachine) -> PyObjectRef {
self.state
.read()
.target
.clone()
.unwrap_or_else(|| vm.ctx.none())
}
#[pygetset]
fn version(&self, vm: &VirtualMachine) -> PyResult<String> {
let expat = vm.import("pyexpat", 0)?;
+34 -36
View File
@@ -6,9 +6,11 @@ pub(crate) use _lzma::module_def;
mod _lzma {
use crate::compression::DecompressorArgs;
use alloc::fmt;
use core::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use rustpython_common::{compression::lzma as backend, lock::PyMutex};
use rustpython_vm::builtins::{PyBaseExceptionRef, PyBytesRef, PyDict, PyType, PyTypeRef};
use rustpython_vm::builtins::{PyBaseExceptionRef, PyBytes, PyDict, PyType, PyTypeRef};
use rustpython_vm::function::ArgBytesLike;
use rustpython_vm::object::PyAtomicRef;
use rustpython_vm::types::Constructor;
use rustpython_vm::{Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine};
@@ -264,22 +266,25 @@ mod _lzma {
struct DecompressorInner {
backend: backend::Decompressor,
unused_data: PyBytesRef,
}
impl DecompressorInner {
fn sync_visible_state(&mut self, vm: &VirtualMachine) {
if self.unused_data.as_bytes() != self.backend.unused_data() {
self.unused_data = vm.ctx.new_bytes(self.backend.unused_data().to_vec());
}
}
}
#[pyattr]
#[pyclass(name = "LZMADecompressor")]
#[pyclass(name = "LZMADecompressor", traverse)]
#[derive(PyPayload)]
struct LZMADecompressor {
#[pytraverse(skip)]
state: PyMutex<DecompressorInner>,
#[pymember]
#[pytraverse(skip)]
check: AtomicI32,
#[pymember]
#[pytraverse(skip)]
eof: AtomicBool,
#[pymember]
#[pytraverse(skip)]
needs_input: AtomicBool,
#[pymember(type = "object_ex")]
unused_data: PyAtomicRef<Option<PyObject>>,
}
impl fmt::Debug for LZMADecompressor {
@@ -314,11 +319,13 @@ mod _lzma {
let filters = filters_to_backend(args.filters, vm)?;
let backend = backend::Decompressor::new(args.format, args.memlimit, filters)
.map_err(|error| map_backend_error(error, vm))?;
let check = backend.check();
Ok(Self {
state: PyMutex::new(DecompressorInner {
backend,
unused_data: vm.ctx.empty_bytes.clone(),
}),
state: PyMutex::new(DecompressorInner { backend }),
check: AtomicI32::new(check),
eof: AtomicBool::new(false),
needs_input: AtomicBool::new(true),
unused_data: PyAtomicRef::from(Some(vm.ctx.empty_bytes.clone().into())),
})
}
}
@@ -331,29 +338,20 @@ mod _lzma {
let data = &*args.data();
let mut state = self.state.lock();
let result = state.backend.decompress(data, max_length);
state.sync_visible_state(vm);
self.check.store(state.backend.check(), Ordering::Relaxed);
self.eof.store(state.backend.eof(), Ordering::Relaxed);
self.needs_input
.store(state.backend.needs_input(), Ordering::Relaxed);
let stale = self.unused_data.deref().is_none_or(|obj| {
obj.downcast_ref::<PyBytes>()
.is_none_or(|bytes| bytes.as_bytes() != state.backend.unused_data())
});
if stale {
let bytes = vm.ctx.new_bytes(state.backend.unused_data().to_vec());
let _previous = unsafe { self.unused_data.swap(Some(bytes.into())) };
}
result.map_err(|error| map_backend_error(error, vm))
}
#[pygetset]
fn check(&self) -> i32 {
self.state.lock().backend.check()
}
#[pygetset]
fn eof(&self) -> bool {
self.state.lock().backend.eof()
}
#[pygetset]
fn unused_data(&self) -> PyBytesRef {
self.state.lock().unused_data.clone()
}
#[pygetset]
fn needs_input(&self) -> bool {
self.state.lock().backend.needs_input()
}
}
#[pyattr]
+106 -51
View File
@@ -1,5 +1,73 @@
pub(crate) use _multiprocessing::module_def;
#[cfg(any(unix, windows))]
const _: () = assert!(
core::mem::size_of::<rustpython_host_env::multiprocessing::SemHandle>()
== core::mem::size_of::<usize>()
&& core::mem::align_of::<rustpython_host_env::multiprocessing::SemHandle>()
== core::mem::align_of::<usize>()
);
/// `SemLock.handle` is one pointer-sized word (`sem_t *` or `HANDLE`).
#[cfg(any(unix, windows))]
#[repr(transparent)]
struct SemHandleWord(rustpython_host_env::multiprocessing::SemHandle);
#[cfg(any(unix, windows))]
impl core::ops::Deref for SemHandleWord {
type Target = rustpython_host_env::multiprocessing::SemHandle;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(any(unix, windows))]
impl core::fmt::Debug for SemHandleWord {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}
#[cfg(any(unix, windows))]
impl crate::vm::builtins::descriptor::MemberLayout for SemHandleWord {
const KIND: crate::vm::builtins::descriptor::MemberKind = {
if cfg!(all(windows, target_pointer_width = "64")) {
crate::vm::builtins::descriptor::MemberKind::ULongLong
} else {
crate::vm::builtins::descriptor::MemberKind::ULong
}
};
}
#[cfg(any(unix, windows))]
fn semlock_name(
name: Option<String>,
) -> (
Option<alloc::ffi::CString>,
crate::vm::builtins::descriptor::CStrMember,
) {
let Some(name) = name else {
return (
None,
crate::vm::builtins::descriptor::CStrMember::new(core::ptr::null()),
);
};
let owner = match alloc::ffi::CString::new(name) {
Ok(owner) => owner,
Err(err) => {
let bytes: Vec<u8> = err
.into_vec()
.into_iter()
.filter(|byte| *byte != 0)
.collect();
alloc::ffi::CString::new(bytes).unwrap_or_default()
}
};
let member = crate::vm::builtins::descriptor::CStrMember::new(owner.as_ptr());
(Some(owner), member)
}
#[cfg(windows)]
#[pymodule]
mod _multiprocessing {
@@ -63,10 +131,17 @@ mod _multiprocessing {
#[pyclass(name = "SemLock", module = "_multiprocessing")]
#[derive(Debug, PyPayload)]
struct SemLock {
handle: SemHandle,
#[pymember]
handle: super::SemHandleWord,
#[pymember]
kind: i32,
#[pymember]
maxvalue: i32,
name: Option<String>,
/// Owns the bytes `name` points at.
#[expect(dead_code, reason = "keeps the semaphore name allocation alive")]
name_owner: Option<alloc::ffi::CString>,
#[pymember]
name: crate::vm::builtins::descriptor::CStrMember,
last_tid: AtomicU32,
count: AtomicI32,
/// Serializes owner bookkeeping. Dropped around blocking waits.
@@ -77,26 +152,6 @@ mod _multiprocessing {
#[pyclass(with(Constructor), flags(BASETYPE))]
impl SemLock {
#[pygetset]
fn handle(&self) -> isize {
self.handle.as_handle_int()
}
#[pygetset]
fn kind(&self) -> i32 {
self.kind
}
#[pygetset]
fn maxvalue(&self) -> i32 {
self.maxvalue
}
#[pygetset]
fn name(&self) -> Option<String> {
self.name.clone()
}
#[pymethod]
fn acquire(&self, args: AcquireArgs, vm: &VirtualMachine) -> PyResult<bool> {
let blocking = args.block;
@@ -237,11 +292,15 @@ mod _multiprocessing {
vm: &VirtualMachine,
) -> PyResult {
// On Windows, _rebuild receives the handle directly (no sem_open)
let (name_owner, name_member) = super::semlock_name(name);
let zelf = Self {
handle: SemHandle::from_raw(handle as host_multiprocessing::RawHandle),
handle: super::SemHandleWord(SemHandle::from_raw(
handle as host_multiprocessing::RawHandle,
)),
kind,
maxvalue,
name,
name_owner,
name: name_member,
last_tid: AtomicU32::new(0),
count: AtomicI32::new(0),
crit: PyMutex::new(()),
@@ -311,14 +370,17 @@ mod _multiprocessing {
return Err(vm.new_value_error("invalid value"));
}
let handle =
SemHandle::create(args.value, args.maxvalue).map_err(|e| e.to_pyexception(vm))?;
let name = if args.unlink { None } else { Some(args.name) };
let handle = super::SemHandleWord(
SemHandle::create(args.value, args.maxvalue).map_err(|e| e.to_pyexception(vm))?,
);
let (name_owner, name) =
super::semlock_name(if args.unlink { None } else { Some(args.name) });
Ok(Self {
handle,
kind: args.kind,
maxvalue: args.maxvalue,
name_owner,
name,
last_tid: AtomicU32::new(0),
count: AtomicI32::new(0),
@@ -462,10 +524,17 @@ mod _multiprocessing {
#[pyclass(name = "SemLock", module = "_multiprocessing")]
#[derive(Debug, PyPayload)]
struct SemLock {
handle: SemHandle,
#[pymember]
handle: super::SemHandleWord,
#[pymember]
kind: i32,
#[pymember]
maxvalue: i32,
name: Option<String>,
/// Owns the bytes `name` points at.
#[expect(dead_code, reason = "keeps the semaphore name allocation alive")]
name_owner: Option<alloc::ffi::CString>,
#[pymember]
name: crate::vm::builtins::descriptor::CStrMember,
last_tid: AtomicU64, // unsigned long
count: AtomicI32, // int
/// Serializes owner bookkeeping. Dropped around blocking waits.
@@ -476,26 +545,6 @@ mod _multiprocessing {
#[pyclass(with(Constructor), flags(BASETYPE))]
impl SemLock {
#[pygetset]
fn handle(&self) -> isize {
self.handle.as_handle_int()
}
#[pygetset]
fn kind(&self) -> i32 {
self.kind
}
#[pygetset]
fn maxvalue(&self) -> i32 {
self.maxvalue
}
#[pygetset]
fn name(&self) -> Option<String> {
self.name.clone()
}
// _multiprocessing_SemLock_acquire_impl
#[pymethod]
fn acquire(&self, args: AcquireArgs, vm: &VirtualMachine) -> PyResult<bool> {
@@ -701,12 +750,16 @@ mod _multiprocessing {
let Some(ref name_str) = name else {
return Err(vm.new_value_error("cannot rebuild SemLock without name"));
};
let handle = SemHandle::open_existing(name_str).map_err(|err| os_error(vm, err))?;
let handle = super::SemHandleWord(
SemHandle::open_existing(name_str).map_err(|err| os_error(vm, err))?,
);
// return newsemlockobject(type, handle, kind, maxvalue, name_copy);
let (name_owner, name) = super::semlock_name(name);
let zelf = Self {
handle,
kind,
maxvalue,
name_owner,
name,
last_tid: AtomicU64::new(0),
count: AtomicI32::new(0),
@@ -828,10 +881,12 @@ mod _multiprocessing {
})?;
// return newsemlockobject(type, handle, kind, maxvalue, name_copy);
let (name_owner, name) = super::semlock_name(name);
Ok(Self {
handle,
handle: super::SemHandleWord(handle),
kind: args.kind,
maxvalue: args.maxvalue,
name_owner,
name,
last_tid: AtomicU64::new(0),
count: AtomicI32::new(0),
+17 -45
View File
@@ -18,6 +18,7 @@ mod _pickle {
protocol::{PyBuffer, PyIter, PyIterReturn},
types::{AsBuffer, Constructor, Initializer, Representable},
};
use core::sync::atomic::{AtomicI32, Ordering};
use malachite_bigint::BigInt;
use num_traits::{ToPrimitive, Zero};
use std::collections::HashMap;
@@ -1872,8 +1873,6 @@ mod _pickle {
pub(super) struct PicklerConfig {
initialized: bool,
proto: u8,
bin: bool,
fast: bool,
fix_imports: bool,
buffer_callback: Option<PyObjectRef>,
}
@@ -1883,8 +1882,6 @@ mod _pickle {
Self {
initialized: false,
proto: DEFAULT_PROTOCOL,
bin: true,
fast: false,
fix_imports: false,
buffer_callback: None,
}
@@ -1943,6 +1940,10 @@ mod _pickle {
out: PyMutex<Output>,
memo: PyRwLock<MemoTable>,
config: PyRwLock<PicklerConfig>,
#[pymember(writable)]
bin: AtomicI32,
#[pymember(writable)]
fast: AtomicI32,
}
#[derive(FromArgs)]
@@ -1989,8 +1990,6 @@ mod _pickle {
Ok(PicklerConfig {
initialized: true,
proto,
bin: proto >= 1,
fast: false,
fix_imports: args.fix_imports && proto < 3,
buffer_callback,
})
@@ -2004,6 +2003,8 @@ mod _pickle {
out: PyMutex::new(Output::default()),
memo: PyRwLock::new(MemoTable::default()),
config: PyRwLock::new(PicklerConfig::default()),
bin: AtomicI32::new(1),
fast: AtomicI32::new(0),
})
}
}
@@ -2028,6 +2029,9 @@ mod _pickle {
..Output::default()
};
zelf.memo.write().clear();
zelf.bin
.store(i32::from(config.proto >= 1), Ordering::Relaxed);
zelf.fast.store(0, Ordering::Relaxed);
*zelf.config.write() = config;
Ok(())
}
@@ -2095,42 +2099,6 @@ mod _pickle {
*zelf.memo.write() = new_memo;
Ok(())
}
#[pygetset]
fn fast(&self) -> bool {
self.config.read().fast
}
#[pygetset(setter)]
fn set_fast(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
match value {
PySetterValue::Assign(v) => {
self.config.write().fast = v.try_to_bool(vm)?;
Ok(())
}
PySetterValue::Delete => {
Err(vm.new_type_error("attribute deletion is not supported"))
}
}
}
#[pygetset]
fn bin(&self) -> bool {
self.config.read().bin
}
#[pygetset(setter)]
fn set_bin(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
match value {
PySetterValue::Assign(v) => {
self.config.write().bin = v.try_to_bool(vm)?;
Ok(())
}
PySetterValue::Delete => {
Err(vm.new_type_error("attribute deletion is not supported"))
}
}
}
}
// saving
@@ -3709,17 +3677,17 @@ mod _pickle {
let Some(mut out) = zelf.out.try_lock() else {
return Err(vm.new_runtime_error("Pickler.dump() called recursively"));
};
let (initialized, proto, bin, fast, fix_imports, buffer_callback) = {
let (initialized, proto, fix_imports, buffer_callback) = {
let cfg = zelf.config.read();
(
cfg.initialized,
cfg.proto,
cfg.bin,
cfg.fast,
cfg.fix_imports,
cfg.buffer_callback.clone(),
)
};
let bin = zelf.bin.load(Ordering::Relaxed) != 0;
let fast = zelf.fast.load(Ordering::Relaxed) != 0;
if !initialized {
return Err(new_pickling_error(
vm,
@@ -3803,6 +3771,8 @@ mod _pickle {
..Output::default()
}),
memo: PyRwLock::new(MemoTable::default()),
bin: AtomicI32::new(i32::from(config.proto >= 1)),
fast: AtomicI32::new(0),
config: PyRwLock::new(config),
}
.into_ref(&vm.ctx);
@@ -3833,6 +3803,8 @@ mod _pickle {
let pickler = PyPickler {
out: PyMutex::new(Output::default()),
memo: PyRwLock::new(MemoTable::default()),
bin: AtomicI32::new(i32::from(config.proto >= 1)),
fast: AtomicI32::new(0),
config: PyRwLock::new(config),
}
.into_ref(&vm.ctx);
+6 -6
View File
@@ -211,7 +211,7 @@ mod _pyexpat {
#[pyattr]
#[pyattr(name = "XMLParserType")]
#[pyclass(name = "xmlparser", module = false, traverse)]
#[pyclass(name = "xmlparser", traverse)]
#[derive(Debug, PyPayload)]
pub(super) struct PyExpatLikeXmlParser {
#[pytraverse(skip)]
@@ -255,7 +255,8 @@ mod _pyexpat {
namespace_prefixes: MutableObject,
ordered_attributes: MutableObject,
specified_attributes: MutableObject,
intern: MutableObject,
#[pymember]
intern: PyObjectRef,
// Additional handlers (stubs for compatibility)
processing_instruction: MutableObject,
unparsed_entity_decl: MutableObject,
@@ -830,7 +831,7 @@ mod _pyexpat {
namespace_prefixes: MutableObject::new(vm.ctx.new_bool(false).into()),
ordered_attributes: MutableObject::new(vm.ctx.new_bool(false).into()),
specified_attributes: MutableObject::new(vm.ctx.new_bool(false).into()),
intern: MutableObject::new(intern_dict),
intern: intern_dict,
// Additional handlers (stubs for compatibility)
processing_instruction: MutableObject::new(vm.ctx.none()),
unparsed_entity_decl: MutableObject::new(vm.ctx.none()),
@@ -890,7 +891,6 @@ mod _pyexpat {
class,
specified_attributes
);
create_property!(ctx, attributes, "intern", class, intern);
create_readonly_int_property!(
ctx,
attributes,
@@ -1066,7 +1066,7 @@ mod _pyexpat {
/// Attribute *values* are intentionally left out of this cache, same
/// as libexpat: they vary far more than names and rarely repeat.
fn intern_name(&self, vm: &VirtualMachine, name: String) -> PyStrRef {
let intern_obj = self.intern.read().clone();
let intern_obj = self.intern.clone();
if let Ok(dict) = intern_obj.downcast::<crate::vm::builtins::PyDict>() {
if let Ok(Some(existing)) = dict.get_item_opt(name.as_str(), vm)
&& let Ok(existing) = existing.downcast::<PyStr>()
@@ -1460,7 +1460,7 @@ mod _pyexpat {
// TODO: Tie this exception to the module's state.
#[pyattr]
#[pyattr(name = "error")]
#[pyexception(name = "ExpatError", base = PyException)]
#[pyexception(name = "ExpatError", module = "xml.parsers.expat", base = PyException)]
#[derive(Debug)]
#[repr(transparent)]
pub(super) struct PyExpatError(PyException);
+43 -74
View File
@@ -692,22 +692,42 @@ mod decl {
Py, PyObject, PyPayload, PyRef,
builtins::{PyFloat, PyType},
class_or_notimplemented,
common::lock::{PyMutex, PyRwLock},
common::lock::PyRwLock,
convert::{IntoPyException, ToPyObject},
function::PyComparisonValue,
types::{Comparable, Constructor, Destructor, PyComparisonOp, Representable},
};
use alloc::sync::Arc;
use core::sync::atomic::AtomicI32;
use core::sync::atomic::{
AtomicI16, AtomicI32, AtomicI64, AtomicU16, AtomicU32, AtomicU64, Ordering,
};
use num_traits::ToPrimitive;
use std::time::Instant;
#[pyclass(module = "select", name = "kevent")]
#[derive(Debug, PyPayload)]
pub(crate) struct PyKevent {
ev: PyMutex<host_select::kqueue::Event>,
#[pymember(writable)]
ident: AtomicU64,
#[pymember(writable)]
filter: AtomicI16,
#[pymember(writable)]
flags: AtomicU16,
#[pymember(writable)]
fflags: AtomicU32,
#[pymember(writable)]
data: AtomicI64,
#[pymember(writable)]
udata: AtomicU64,
}
const _: () = assert!(
core::mem::size_of::<usize>() == core::mem::size_of::<u64>()
&& core::mem::size_of::<isize>() == core::mem::size_of::<i64>()
&& core::mem::size_of::<std::os::raw::c_ulong>() == core::mem::size_of::<u64>()
&& core::mem::size_of::<std::os::raw::c_long>() == core::mem::size_of::<i64>()
);
#[derive(FromArgs)]
pub(crate) struct KeventNewArgs {
#[pyarg(any)]
@@ -741,14 +761,12 @@ mod decl {
fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
let ident = ident_from_object(&args.ident, vm)?;
Ok(Self {
ev: PyMutex::new(host_select::kqueue::Event {
ident,
filter: args.filter,
flags: args.flags,
fflags: args.fflags,
data: args.data,
udata: args.udata,
}),
ident: AtomicU64::new(ident as u64),
filter: AtomicI16::new(args.filter),
flags: AtomicU16::new(args.flags),
fflags: AtomicU32::new(args.fflags),
data: AtomicI64::new(args.data as i64),
udata: AtomicU64::new(args.udata as u64),
})
}
}
@@ -756,75 +774,26 @@ mod decl {
#[pyclass(with(Constructor, Comparable, Representable))]
impl PyKevent {
pub(super) fn event(&self) -> host_select::kqueue::Event {
*self.ev.lock()
host_select::kqueue::Event {
ident: self.ident.load(Ordering::Relaxed) as usize,
filter: self.filter.load(Ordering::Relaxed),
flags: self.flags.load(Ordering::Relaxed),
fflags: self.fflags.load(Ordering::Relaxed),
data: self.data.load(Ordering::Relaxed) as isize,
udata: self.udata.load(Ordering::Relaxed) as usize,
}
}
pub(super) fn from_event(ev: host_select::kqueue::Event) -> Self {
Self {
ev: PyMutex::new(ev),
ident: AtomicU64::new(ev.ident as u64),
filter: AtomicI16::new(ev.filter),
flags: AtomicU16::new(ev.flags),
fflags: AtomicU32::new(ev.fflags),
data: AtomicI64::new(ev.data as i64),
udata: AtomicU64::new(ev.udata as u64),
}
}
#[pygetset]
fn ident(&self) -> usize {
self.ev.lock().ident
}
#[pygetset(setter)]
fn set_ident(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
self.ev.lock().ident = ident_from_object(&value, vm)?;
Ok(())
}
#[pygetset]
fn filter(&self) -> i16 {
self.ev.lock().filter
}
#[pygetset(setter)]
fn set_filter(&self, value: i16) {
self.ev.lock().filter = value;
}
#[pygetset]
fn flags(&self) -> u16 {
self.ev.lock().flags
}
#[pygetset(setter)]
fn set_flags(&self, value: u16) {
self.ev.lock().flags = value;
}
#[pygetset]
fn fflags(&self) -> u32 {
self.ev.lock().fflags
}
#[pygetset(setter)]
fn set_fflags(&self, value: u32) {
self.ev.lock().fflags = value;
}
#[pygetset]
fn data(&self) -> isize {
self.ev.lock().data
}
#[pygetset(setter)]
fn set_data(&self, value: isize) {
self.ev.lock().data = value;
}
#[pygetset]
fn udata(&self) -> usize {
self.ev.lock().udata
}
#[pygetset(setter)]
fn set_udata(&self, value: usize) {
self.ev.lock().udata = value;
}
}
impl Comparable for PyKevent {
+44 -38
View File
@@ -44,6 +44,7 @@ mod _socket {
Ok(())
}
use core::sync::atomic::{AtomicI32, Ordering};
use core::{
mem::ManuallyDrop,
net::{Ipv4Addr, Ipv6Addr, SocketAddr},
@@ -1062,9 +1063,12 @@ mod _socket {
#[pyclass(name = "socket")]
#[derive(Debug, PyPayload)]
pub struct PySocket {
kind: AtomicCell<i32>,
family: AtomicCell<i32>,
proto: AtomicCell<i32>,
#[pymember(name = "type")]
kind: AtomicI32,
#[pymember]
family: AtomicI32,
#[pymember]
proto: AtomicI32,
pub(crate) timeout: AtomicCell<f64>,
sock: PyRwLock<Option<Socket>>,
}
@@ -1074,9 +1078,9 @@ mod _socket {
impl Default for PySocket {
fn default() -> Self {
Self {
kind: AtomicCell::default(),
family: AtomicCell::default(),
proto: AtomicCell::default(),
kind: AtomicI32::new(0),
family: AtomicI32::new(0),
proto: AtomicI32::new(0),
timeout: AtomicCell::new(-1.0),
sock: PyRwLock::new(None),
}
@@ -1137,7 +1141,7 @@ mod _socket {
proto: i32,
sock: Socket,
) -> io::Result<()> {
self.family.store(family);
self.family.store(family, Ordering::Relaxed);
// Mask out SOCK_NONBLOCK and SOCK_CLOEXEC flags from stored type
// to ensure consistent cross-platform behavior
#[cfg(any(
@@ -1164,8 +1168,8 @@ mod _socket {
target_os = "redox"
)))]
let masked_kind = socket_kind;
self.kind.store(masked_kind);
self.proto.store(proto);
self.kind.store(masked_kind, Ordering::Relaxed);
self.proto.store(proto, Ordering::Relaxed);
let mut s = self.sock.write();
let sock = s.insert(sock);
// If SOCK_NONBLOCK is set, use timeout 0 (non-blocking)
@@ -1272,7 +1276,7 @@ mod _socket {
caller: &str,
vm: &VirtualMachine,
) -> Result<host_socket::raw::SockAddr, IoOrPyException> {
let family = self.family.load();
let family = self.family.load(Ordering::Relaxed);
match family {
#[cfg(unix)]
c::AF_UNIX => {
@@ -1338,11 +1342,11 @@ mod _socket {
}
#[cfg(windows)]
family if family == c::AF_HYPERV => {
if self.proto.load() != host_socket::HV_PROTOCOL_RAW {
if self.proto.load(Ordering::Relaxed) != host_socket::HV_PROTOCOL_RAW {
return Err(vm
.new_os_error(format!(
"{caller}(): unsupported AF_HYPERV protocol: {}",
self.proto.load()
self.proto.load(Ordering::Relaxed)
))
.into());
}
@@ -1403,7 +1407,7 @@ mod _socket {
}
#[cfg(windows)]
family if family == c::AF_BLUETOOTH => {
if self.proto.load() != host_socket::BTHPROTO_RFCOMM {
if self.proto.load(Ordering::Relaxed) != host_socket::BTHPROTO_RFCOMM {
return Err(vm
.new_os_error(format!("{caller}(): unknown Bluetooth protocol"))
.into());
@@ -1448,7 +1452,7 @@ mod _socket {
obj.class().name()
))
})?;
let proto = self.proto.load();
let proto = self.proto.load(Ordering::Relaxed);
let interface: PyStrRef = tuple
.as_slice()
.first()
@@ -1697,9 +1701,9 @@ mod _socket {
Ok(format!(
"<socket object, fd={}, family={}, type={}, proto={}>",
zelf.fileno(),
zelf.family.load(),
zelf.kind.load(),
zelf.proto.load(),
zelf.family.load(Ordering::Relaxed),
zelf.kind.load(Ordering::Relaxed),
zelf.proto.load(Ordering::Relaxed),
))
}
}
@@ -1896,7 +1900,10 @@ mod _socket {
self.sock_snapshot()?.accept_raw()
})?;
let fd = into_sock_fileno(sock);
Ok((fd, get_addr_tuple(&addr, self.proto.load(), vm)))
Ok((
fd,
get_addr_tuple(&addr, self.proto.load(Ordering::Relaxed), vm),
))
}
#[pymethod]
@@ -1969,7 +1976,10 @@ mod _socket {
.recv_from_with_flags(buffer.spare_capacity_mut(), flags)
})?;
unsafe { buffer.set_len(n) };
Ok((buffer, get_addr_tuple(&addr, self.proto.load(), vm)))
Ok((
buffer,
get_addr_tuple(&addr, self.proto.load(Ordering::Relaxed), vm),
))
}
#[pymethod]
@@ -2002,7 +2012,10 @@ mod _socket {
})?;
unsafe { scratch.set_len(n) };
buf.borrow_buf_mut()[..n].copy_from_slice(&scratch);
Ok((n, get_addr_tuple(&addr, self.proto.load(), vm)))
Ok((
n,
get_addr_tuple(&addr, self.proto.load(Ordering::Relaxed), vm),
))
}
#[pymethod]
@@ -2226,7 +2239,7 @@ mod _socket {
let storage: host_socket::raw::SockAddrStorage =
unsafe { core::mem::transmute(address.storage) };
let addr = unsafe { host_socket::raw::SockAddr::new(storage, address.len as _) };
get_addr_tuple(&addr, self.proto.load(), vm)
get_addr_tuple(&addr, self.proto.load(Ordering::Relaxed), vm)
} else {
vm.ctx.none()
};
@@ -2310,14 +2323,22 @@ mod _socket {
fn getsockname(&self, vm: &VirtualMachine) -> std::io::Result<PyObjectRef> {
let addr = self.sock()?.local_addr()?;
Ok(get_addr_tuple(&addr, self.proto.load(), vm))
Ok(get_addr_tuple(
&addr,
self.proto.load(Ordering::Relaxed),
vm,
))
}
#[pymethod]
fn getpeername(&self, vm: &VirtualMachine) -> std::io::Result<PyObjectRef> {
let addr = self.sock()?.peer_addr()?;
Ok(get_addr_tuple(&addr, self.proto.load(), vm))
Ok(get_addr_tuple(
&addr,
self.proto.load(Ordering::Relaxed),
vm,
))
}
#[pymethod]
@@ -2507,21 +2528,6 @@ mod _socket {
let fd = sock_fileno(&sock);
host_socket::share_socket(fd as _, process_id).map_err(Into::into)
}
#[pygetset(name = "type")]
fn kind(&self) -> i32 {
self.kind.load()
}
#[pygetset]
fn family(&self) -> i32 {
self.family.load()
}
#[pygetset]
fn proto(&self) -> i32 {
self.proto.load()
}
}
struct Address {
+3 -15
View File
@@ -207,8 +207,11 @@ mod _socket {
#[pyclass(name = "socket")]
#[derive(Debug, PyPayload)]
struct PySocket {
#[pymember]
family: AtomicI32,
#[pymember(name = "type")]
kind: AtomicI32,
#[pymember]
proto: AtomicI32,
timeout: PyMutex<Option<f64>>,
closed: PyMutex<bool>,
@@ -235,21 +238,6 @@ mod _socket {
Ok(())
}
#[pygetset]
fn family(&self) -> i32 {
self.family.load(Ordering::Relaxed)
}
#[pygetset]
fn r#type(&self) -> i32 {
self.kind.load(Ordering::Relaxed)
}
#[pygetset]
fn proto(&self) -> i32 {
self.proto.load(Ordering::Relaxed)
}
#[pymethod]
fn fileno(&self) -> i32 {
if *self.closed.lock() { -1 } else { 0 }
+7 -7
View File
@@ -18,7 +18,7 @@ pub(crate) mod ssl_error {
};
#[pyattr]
#[pyexception(name = "SSLError", base = PyOSError)]
#[pyexception(name = "SSLError", module = "ssl", base = PyOSError)]
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct PySSLError(PyOSError);
@@ -47,7 +47,7 @@ pub(crate) mod ssl_error {
}
#[pyattr]
#[pyexception(name = "SSLZeroReturnError", base = PySSLError)]
#[pyexception(name = "SSLZeroReturnError", module = "ssl", base = PySSLError)]
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct PySSLZeroReturnError(PySSLError);
@@ -56,31 +56,31 @@ pub(crate) mod ssl_error {
impl PySSLZeroReturnError {}
#[pyattr]
#[pyexception(name = "SSLWantReadError", base = PySSLError, impl)]
#[pyexception(name = "SSLWantReadError", module = "ssl", base = PySSLError, impl)]
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct PySSLWantReadError(PySSLError);
#[pyattr]
#[pyexception(name = "SSLWantWriteError", base = PySSLError, impl)]
#[pyexception(name = "SSLWantWriteError", module = "ssl", base = PySSLError, impl)]
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct PySSLWantWriteError(PySSLError);
#[pyattr]
#[pyexception(name = "SSLSyscallError", base = PySSLError, impl)]
#[pyexception(name = "SSLSyscallError", module = "ssl", base = PySSLError, impl)]
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct PySSLSyscallError(PySSLError);
#[pyattr]
#[pyexception(name = "SSLEOFError", base = PySSLError, impl)]
#[pyexception(name = "SSLEOFError", module = "ssl", base = PySSLError, impl)]
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct PySSLEOFError(PySSLError);
#[pyattr]
#[pyexception(name = "SSLCertVerificationError", base = PySSLError, impl)]
#[pyexception(name = "SSLCertVerificationError", module = "ssl", base = PySSLError, impl)]
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct PySSLCertVerificationError(PySSLError);
+16 -6
View File
@@ -72,12 +72,27 @@ mod unicodedata {
#[derive(Debug, PyPayload)]
pub(super) struct Ucd {
inner: unicode_core::Ucd,
/// Owns the bytes `unidata_version` points at.
#[expect(dead_code, reason = "keeps the version string alive")]
version_owner: alloc::ffi::CString,
#[pymember]
unidata_version: crate::vm::builtins::descriptor::CStrMember,
}
impl Ucd {
pub(super) const fn new(modern: bool) -> Self {
pub(super) fn new(modern: bool) -> Self {
let text = if modern {
unicode_core::unicode_version()
} else {
"3.2.0".to_owned()
};
let version_owner = alloc::ffi::CString::new(text).unwrap_or_default();
let unidata_version =
crate::vm::builtins::descriptor::CStrMember::new(version_owner.as_ptr());
Self {
inner: unicode_core::Ucd::new(modern),
version_owner,
unidata_version,
}
}
@@ -217,11 +232,6 @@ mod unicodedata {
.map(Option::Some)
.ok_or_else(|| vm.new_value_error("not a numeric character"))
}
#[pygetset]
fn unidata_version(&self) -> String {
self.inner.unidata_version()
}
}
#[pyattr]
+25 -23
View File
@@ -7,14 +7,16 @@ mod zlib {
use crate::compression::{DecompressArgs, DecompressorArgs};
use crate::vm::{
Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{PyBaseExceptionRef, PyBytesRef, PyType, PyTypeRef},
builtins::{PyBaseExceptionRef, PyBytes, PyBytesRef, PyType, PyTypeRef},
common::lock::PyMutex,
convert::TryFromBorrowedObject,
function::{ArgBytesLike, ArgIndex, OptionalArg, PySize, PySsize},
object::PyAtomicRef,
types::Constructor,
};
use adler32::RollingAdler32 as Adler32;
use alloc::fmt;
use core::sync::atomic::{AtomicBool, Ordering};
use rustpython_common::compression::zlib as backend;
#[pyattr]
@@ -416,7 +418,6 @@ mod zlib {
struct PyZlibDecompressorInner {
decompress: backend::ZlibDecompressor,
unused_data: PyBytesRef,
}
#[pyattr]
@@ -425,6 +426,14 @@ mod zlib {
struct ZlibDecompressor {
#[pytraverse(skip)]
inner: PyMutex<PyZlibDecompressorInner>,
#[pymember]
#[pytraverse(skip)]
eof: AtomicBool,
#[pymember]
#[pytraverse(skip)]
needs_input: AtomicBool,
#[pymember(type = "object_ex")]
unused_data: PyAtomicRef<Option<PyObject>>,
}
impl fmt::Debug for ZlibDecompressor {
@@ -450,31 +459,16 @@ mod zlib {
let decompress = backend::ZlibDecompressor::new(args.wbits, owned_dict(args.zdict))
.map_err(|err| new_init_or_zlib_error(err, vm))?;
Ok(Self {
inner: PyMutex::new(PyZlibDecompressorInner {
decompress,
unused_data: vm.ctx.empty_bytes.clone(),
}),
inner: PyMutex::new(PyZlibDecompressorInner { decompress }),
eof: AtomicBool::new(false),
needs_input: AtomicBool::new(true),
unused_data: PyAtomicRef::from(Some(vm.ctx.empty_bytes.clone().into())),
})
}
}
#[pyclass(with(Constructor))]
impl ZlibDecompressor {
#[pygetset]
fn eof(&self) -> bool {
self.inner.lock().decompress.eof()
}
#[pygetset]
fn unused_data(&self) -> PyBytesRef {
self.inner.lock().unused_data.clone()
}
#[pygetset]
fn needs_input(&self) -> bool {
self.inner.lock().decompress.needs_input()
}
#[pymethod]
fn decompress(&self, args: DecompressorArgs, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
let max_length = args.max_length();
@@ -482,8 +476,16 @@ mod zlib {
let mut inner = self.inner.lock();
let result = inner.decompress.decompress(data, max_length);
if inner.unused_data.as_bytes() != inner.decompress.unused_data() {
inner.unused_data = vm.ctx.new_bytes(inner.decompress.unused_data().to_vec());
self.eof.store(inner.decompress.eof(), Ordering::Relaxed);
self.needs_input
.store(inner.decompress.needs_input(), Ordering::Relaxed);
let stale = self.unused_data.deref().is_none_or(|obj| {
obj.downcast_ref::<PyBytes>()
.is_none_or(|bytes| bytes.as_bytes() != inner.decompress.unused_data())
});
if stale {
let bytes = vm.ctx.new_bytes(inner.decompress.unused_data().to_vec());
let _previous = unsafe { self.unused_data.swap(Some(bytes.into())) };
}
result.map_err(|err| match err {
backend::DecompressError::Zlib(err) => new_zlib_error(err, vm),
+1
View File
@@ -60,6 +60,7 @@ impl PyPayload for PyAsyncGen {
}
#[pyclass(
itemsize = core::mem::size_of::<crate::PyObjectRef>(),
flags(DISALLOW_INSTANTIATION, HAS_WEAKREF),
with(PyRef, Representable, Destructor)
)]
+14 -12
View File
@@ -18,8 +18,8 @@ pub struct PyNativeFunction {
pub(crate) zelf: Option<PyObjectRef>,
// Module that owns this function. Not passed as a call argument.
pub(crate) module_object: Option<PyObjectRef>,
#[pytraverse(skip)]
pub(crate) module: Option<&'static PyStrInterned>, // None for bound method
#[pymember(name = "__module__", writable)]
pub(crate) module: crate::object::PyAtomicRef<Option<PyObject>>,
/// Prevent HeapMethodDef from being freed while this function references it
pub(crate) _method_def_owner: Option<PyObjectRef>,
}
@@ -32,11 +32,18 @@ impl PyPayload for PyNativeFunction {
impl fmt::Debug for PyNativeFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let module = match self
.module
.deref()
.and_then(|m| m.downcast_ref::<crate::builtins::PyStr>())
{
Some(module) => module.as_wtf8().to_owned(),
None => Wtf8::new("<unknown>").to_owned(),
};
write!(
f,
"builtin function {}.{} ({:?}) self as instance of {:?}",
self.module
.map_or_else(|| Wtf8::new("<unknown>"), |m| m.as_wtf8()),
module,
self.value.name,
self.value.flags,
self.zelf.as_ref().map(|z| z.class().name().to_owned())
@@ -45,8 +52,8 @@ impl fmt::Debug for PyNativeFunction {
}
impl PyNativeFunction {
pub const fn with_module(mut self, module: &'static PyStrInterned) -> Self {
self.module = Some(module);
pub fn with_module(self, module: &'static PyStrInterned) -> Self {
drop(self.module.store(Some(module.to_owned().into())));
self
}
@@ -149,11 +156,6 @@ impl Representable for PyNativeFunction {
flags(HAS_WEAKREF, DISALLOW_INSTANTIATION)
)]
impl PyNativeFunction {
#[pygetset]
fn __module__(zelf: NativeFunctionOrMethod) -> Option<&'static PyStrInterned> {
zelf.0.module
}
#[pygetset]
fn __name__(zelf: NativeFunctionOrMethod) -> &'static str {
zelf.0.value.name
@@ -203,7 +205,7 @@ impl PyNativeFunction {
#[pymethod]
fn __reduce__(zelf: NativeFunctionOrMethod, vm: &VirtualMachine) -> PyResult {
let zelf = zelf.0;
if zelf.zelf.is_none() || zelf.module.is_some() {
if zelf.zelf.is_none() || zelf.module.deref().is_some() {
Ok(vm.ctx.new_str(zelf.value.name).into())
} else {
let getattr = vm.builtins.get_attr("getattr", vm)?;
+2 -4
View File
@@ -184,15 +184,13 @@ impl Representable for PyClassMethod {
let callable = zelf.callable.load_owned().repr(vm)?;
let class = Self::class(&vm.ctx);
let module = class.__module__(vm)?;
let repr = match (
class
.__qualname__(vm)
.downcast_ref::<PyStr>()
.map(|n| n.as_wtf8()),
class
.__module__(vm)
.downcast_ref::<PyStr>()
.map(|m| m.as_wtf8()),
module.downcast_ref::<PyStr>().map(|m| m.as_wtf8()),
) {
(None, _) => return Err(vm.new_type_error("Unknown qualified name")),
(Some(qualname), Some(module)) if module != "builtins" => {
+12 -11
View File
@@ -1,5 +1,6 @@
//! Infamous code object. The python class `code`
use super::descriptor::{MemberKind, MemberLayout};
use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType, set::PyFrozenSet};
use crate::common::lock::PyMutex;
#[cfg(feature = "host_env")]
@@ -477,12 +478,16 @@ pub struct PyCode {
#[pymember(name = "co_stacksize", path = "max_stackdepth")]
#[pymember(name = "co_name", path = "obj_name")]
#[pymember(name = "co_qualname", path = "qualname")]
#[pymember(name = "co_flags", path = "flags")]
pub code: CodeObject,
/// Slot-indexed names, equivalent to CPython's `co_localsplusnames`.
/// Derived once so frame-local proxy operations do not repeatedly scan
/// merged cell variables.
localsplus_names: Box<[&'static PyStrInterned]>,
#[pymember(name = "co_filename")]
source_path: AtomicPtr<PyStrInterned>,
#[pymember(name = "co_nlocals")]
nlocals: i32,
/// Version counter for lazy re-instrumentation.
/// Compared against `PyGlobalState::instrumentation_version` at RESUME.
pub instrumentation_version: AtomicU64,
@@ -582,9 +587,14 @@ fn build_localspluskinds(
Ok(kinds.into_boxed_slice())
}
impl MemberLayout for CodeFlags {
const KIND: MemberKind = MemberKind::Int;
}
impl PyCode {
pub fn new(code: CodeObject) -> Self {
let sp = code.source_path as *const PyStrInterned as *mut PyStrInterned;
let nlocals = i32::try_from(code.varnames.len()).unwrap_or(i32::MAX);
let localsplus_names = {
let varname_ids = code
.varnames
@@ -622,6 +632,7 @@ impl PyCode {
code,
localsplus_names,
source_path: AtomicPtr::new(sp),
nlocals,
instrumentation_version: AtomicU64::new(0),
monitoring_data: PyMutex::new(None),
quickened: core::sync::atomic::AtomicBool::new(false),
@@ -1030,11 +1041,11 @@ impl Constructor for PyCode {
}
#[pyclass(
itemsize = core::mem::size_of::<u16>(),
with(Representable, Constructor, Comparable, Hashable),
flags(HAS_WEAKREF)
)]
impl PyCode {
#[pygetset]
pub fn co_filename(&self) -> PyStrRef {
self.source_path().to_owned()
}
@@ -1049,11 +1060,6 @@ impl PyCode {
vm.ctx.new_tuple(cellvars)
}
#[pygetset]
fn co_nlocals(&self) -> usize {
self.code.varnames.len()
}
#[pygetset]
fn co_firstlineno(&self) -> u32 {
self.code.first_line_number.map_or(0, |n| n.get() as _)
@@ -1077,11 +1083,6 @@ impl PyCode {
vm.ctx.new_tuple(names)
}
#[pygetset]
const fn co_flags(&self) -> u32 {
self.code.flags.bits()
}
#[pygetset]
pub fn co_varnames(&self, vm: &VirtualMachine) -> PyTupleRef {
let varnames = self.code.varnames.iter().map(|s| s.to_object()).collect();
+1
View File
@@ -42,6 +42,7 @@ impl PyPayload for PyCoroutine {
}
#[pyclass(
itemsize = core::mem::size_of::<crate::PyObjectRef>(),
flags(DISALLOW_INSTANTIATION, HAS_WEAKREF),
with(Py, Representable, Destructor)
)]
+505 -7
View File
@@ -17,6 +17,7 @@ use crate::{
SetattroFunc, StringifyFunc,
},
};
use core::mem::{align_of, size_of};
use rustpython_common::lock::PyRwLock;
#[derive(Debug)]
@@ -313,26 +314,64 @@ impl Representable for PyClassMethodDescriptor {
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(i32)]
pub enum MemberKind {
/// `short`. Writable cells are `AtomicI16`.
Short = 0,
Int = 1,
/// `long` (`c_long`). Writable cells are the atomic of that width.
Long = 2,
Double = 4,
/// `char *`. The slot is a pointer to a NUL-terminated string. Null loads
/// as `None`. Assignment is rejected.
String = 5,
Object = 6,
/// `unsigned short`. Writable cells are `AtomicU16`.
UShort = 10,
Uint = 11,
/// `unsigned long` (`c_ulong`). Writable cells are the atomic of that width.
ULong = 12,
Bool = 14,
ObjectEx = 16,
/// `long long`. Writable cells are `AtomicI64`.
LongLong = 17,
/// `unsigned long long`. Writable cells are `AtomicU64`.
ULongLong = 18,
/// `Py_ssize_t`. Writable cells are `AtomicIsize`.
PySsizeT = 19,
}
impl MemberKind {
const fn from_i64() -> Self {
if size_of::<core::ffi::c_long>() == 8 {
Self::Long
} else {
Self::LongLong
}
}
const fn from_u64() -> Self {
if size_of::<core::ffi::c_ulong>() == 8 {
Self::ULong
} else {
Self::ULongLong
}
}
#[must_use]
pub fn from_i32(value: i32) -> Option<Self> {
match value {
0 => Some(Self::Short),
1 => Some(Self::Int),
2 => Some(Self::Long),
4 => Some(Self::Double),
5 => Some(Self::String),
6 => Some(Self::Object),
10 => Some(Self::UShort),
11 => Some(Self::Uint),
12 => Some(Self::ULong),
14 => Some(Self::Bool),
16 => Some(Self::ObjectEx),
17 => Some(Self::LongLong),
18 => Some(Self::ULongLong),
19 => Some(Self::PySsizeT),
_ => None,
}
@@ -363,6 +402,75 @@ pub const fn member_kind_of<T: MemberLayout, Owner>(
<T as MemberLayout>::KIND
}
impl MemberLayout for i16 {
const KIND: MemberKind = MemberKind::Short;
}
impl MemberLayout for core::sync::atomic::AtomicI16 {
const KIND: MemberKind = MemberKind::Short;
}
impl MemberCell for core::sync::atomic::AtomicI16 {}
impl MemberLayout for u16 {
const KIND: MemberKind = MemberKind::UShort;
}
impl MemberLayout for core::sync::atomic::AtomicU16 {
const KIND: MemberKind = MemberKind::UShort;
}
impl MemberCell for core::sync::atomic::AtomicU16 {}
// `i64` is `long` where `c_long` is 8 bytes, and `long long` otherwise.
impl MemberLayout for i64 {
const KIND: MemberKind = MemberKind::from_i64();
}
impl MemberLayout for core::sync::atomic::AtomicI64 {
const KIND: MemberKind = MemberKind::from_i64();
}
impl MemberCell for core::sync::atomic::AtomicI64 {}
impl MemberLayout for u64 {
const KIND: MemberKind = MemberKind::from_u64();
}
impl MemberLayout for core::sync::atomic::AtomicU64 {
const KIND: MemberKind = MemberKind::from_u64();
}
impl MemberCell for core::sync::atomic::AtomicU64 {}
/// Readonly `char *`. The word is the pointer, not the characters.
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct CStrMember(*const core::ffi::c_char);
impl core::fmt::Debug for CStrMember {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CStrMember").field("ptr", &self.0).finish()
}
}
impl CStrMember {
#[must_use]
pub const fn new(ptr: *const core::ffi::c_char) -> Self {
Self(ptr)
}
#[must_use]
pub const fn as_ptr(self) -> *const core::ffi::c_char {
self.0
}
}
// The pointer is not dereferenced by moving the wrapper.
unsafe impl Send for CStrMember {}
unsafe impl Sync for CStrMember {}
const _: () = assert!(
size_of::<CStrMember>() == size_of::<*const core::ffi::c_char>()
&& align_of::<CStrMember>() == align_of::<*const core::ffi::c_char>()
);
impl MemberLayout for CStrMember {
const KIND: MemberKind = MemberKind::String;
}
impl MemberLayout for bool {
const KIND: MemberKind = MemberKind::Bool;
}
@@ -405,6 +513,13 @@ impl<T> MemberLayout for Option<PyRef<T>> {
impl MemberLayout for crate::object::PyAtomicRef<PyObject> {
const KIND: MemberKind = MemberKind::Object;
}
// `PyAtomicRef<T>` is the same pointer-sized cell as `PyAtomicRef<PyObject>`
// (`PhantomData<T>` is zero-sized). Readonly object members may use it. The
// getter loads the slot as an object pointer. Writable cells stay
// `PyAtomicRef<PyObject>` or `PyAtomicRef<Option<PyObject>>` (`MemberCell`).
impl<T: PyPayload> MemberLayout for crate::object::PyAtomicRef<T> {
const KIND: MemberKind = MemberKind::Object;
}
impl MemberLayout for crate::object::PyAtomicRef<Option<PyObject>> {
const KIND: MemberKind = MemberKind::Object;
}
@@ -436,6 +551,34 @@ impl MemberLayout for core::sync::atomic::AtomicIsize {
}
impl MemberCell for core::sync::atomic::AtomicIsize {}
// `usize` and `isize` are the same width. A readonly `Py_ssize_t` member may
// be a `usize` that is not written after publication and whose value fits in
// `isize` (the getter reads the bits as `isize`).
const _: () =
assert!(size_of::<usize>() == size_of::<isize>() && align_of::<usize>() == align_of::<isize>());
impl MemberLayout for usize {
const KIND: MemberKind = MemberKind::PySsizeT;
}
// Readonly `co_filename`. The field is `AtomicPtr<PyStrInterned>`: the pointer
// is never null, and `PyStrInterned` is `repr(transparent)` over `Py<PyStr>`,
// so the word is an object pointer. `member_get_one` reads an Object member
// through `PyAtomicRef<Option<PyObject>>` (`get_slot`). That cell is
// `AtomicPtr<u8>` when `threading` is on and `Cell<*mut u8>` otherwise; both
// are one pointer, and `AtomicPtr<PyStrInterned>` is one pointer in either
// build (`Atomic<*mut T>` stores `Align8<*mut T>`, same size and alignment as
// the pointer). The load copies that word and increfs. Interned strings live
// for the process, and the slot does not own the reference.
const _: () = assert!(
size_of::<core::sync::atomic::AtomicPtr<PyStrInterned>>()
== size_of::<crate::object::PyAtomicRef<Option<PyObject>>>()
&& align_of::<core::sync::atomic::AtomicPtr<PyStrInterned>>()
== align_of::<crate::object::PyAtomicRef<Option<PyObject>>>()
);
impl MemberLayout for core::sync::atomic::AtomicPtr<PyStrInterned> {
const KIND: MemberKind = MemberKind::Object;
}
/// Where `PyMemberDef.offset` points.
///
/// `Offset` is a byte offset from the object to the field.
@@ -447,6 +590,18 @@ pub enum MemberAccess {
TupleItem,
}
/// C layout of `PyMemberDef`: name pointer, int type, `Py_ssize_t` offset,
/// int flags, doc pointer. `size_of` of this is `type`'s `tp_itemsize`.
#[repr(C)]
#[allow(dead_code)]
pub struct PyMemberDefLayout {
_name: *const core::ffi::c_char,
_type: core::ffi::c_int,
_offset: isize,
_flags: core::ffi::c_int,
_doc: *const core::ffi::c_char,
}
/// Same fields as `PyMemberDef`: name, type, offset, flags, doc.
pub struct PyMemberDef {
pub name: String,
@@ -539,7 +694,14 @@ impl PyMemberDescriptor {
MemberKind::Bool
| MemberKind::Double
| MemberKind::Int
| MemberKind::Long
| MemberKind::LongLong
| MemberKind::Short
| MemberKind::String
| MemberKind::Uint
| MemberKind::ULong
| MemberKind::ULongLong
| MemberKind::UShort
| MemberKind::PySsizeT => None,
}
}
@@ -689,6 +851,168 @@ fn load_i32(obj: &PyObject, offset: isize, readonly: bool) -> i32 {
}
}
fn load_i16(obj: &PyObject, offset: isize, readonly: bool) -> i16 {
let addr = member_addr(obj, offset);
if readonly {
// SAFETY: a readonly short member addresses an `i16` that is not written
// after publication.
unsafe { addr.cast::<i16>().read() }
} else {
// SAFETY: a writable short member addresses an aligned `AtomicI16`.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicI16>())
.load(core::sync::atomic::Ordering::Relaxed)
}
}
}
fn load_u16(obj: &PyObject, offset: isize, readonly: bool) -> u16 {
let addr = member_addr(obj, offset);
if readonly {
// SAFETY: a readonly unsigned short member addresses a `u16` that is not
// written after publication.
unsafe { addr.cast::<u16>().read() }
} else {
// SAFETY: a writable unsigned short member addresses an aligned `AtomicU16`.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicU16>())
.load(core::sync::atomic::Ordering::Relaxed)
}
}
}
fn load_i64(obj: &PyObject, offset: isize, readonly: bool) -> i64 {
let addr = member_addr(obj, offset);
if readonly {
// SAFETY: a readonly `long long` member addresses an `i64` that is not
// written after publication.
unsafe { addr.cast::<i64>().read() }
} else {
// SAFETY: a writable `long long` member addresses an aligned `AtomicI64`.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicI64>())
.load(core::sync::atomic::Ordering::Relaxed)
}
}
}
fn load_u64(obj: &PyObject, offset: isize, readonly: bool) -> u64 {
let addr = member_addr(obj, offset);
if readonly {
// SAFETY: a readonly `unsigned long long` member is a `u64` or an
// `AtomicU64` of the same size and alignment. A relaxed atomic load
// reads either; a plain integer is not written after publication, and
// an atomic word (type flags) is updated in place.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicU64>())
.load(core::sync::atomic::Ordering::Relaxed)
}
} else {
// SAFETY: a writable `unsigned long long` member addresses an aligned `AtomicU64`.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicU64>())
.load(core::sync::atomic::Ordering::Relaxed)
}
}
}
#[allow(clippy::unnecessary_cast)] // `c_long` is `i32` or `i64`
fn load_c_long(obj: &PyObject, offset: isize, readonly: bool) -> core::ffi::c_long {
let addr = member_addr(obj, offset);
if readonly {
// SAFETY: a readonly `long` member addresses a `c_long` that is not
// written after publication.
unsafe { addr.cast::<core::ffi::c_long>().read() }
} else if size_of::<core::ffi::c_long>() == 8 {
// SAFETY: a writable `long` member addresses an aligned `AtomicI64`.
// `c_long` is 8 bytes on this target.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicI64>())
.load(core::sync::atomic::Ordering::Relaxed) as core::ffi::c_long
}
} else {
// SAFETY: a writable `long` member addresses an aligned `AtomicI32`.
// `c_long` is 4 bytes on this target.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicI32>())
.load(core::sync::atomic::Ordering::Relaxed) as core::ffi::c_long
}
}
}
#[allow(clippy::unnecessary_cast)] // `c_long` is `i32` or `i64`
fn store_c_long(obj: &PyObject, offset: isize, value: core::ffi::c_long) {
let addr = member_addr(obj, offset);
if size_of::<core::ffi::c_long>() == 8 {
// SAFETY: a writable `long` member addresses an aligned `AtomicI64`.
// `c_long` is 8 bytes on this target.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicI64>())
.store(value as i64, core::sync::atomic::Ordering::Relaxed);
}
} else {
// SAFETY: a writable `long` member addresses an aligned `AtomicI32`.
// `c_long` is 4 bytes on this target.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicI32>())
.store(value as i32, core::sync::atomic::Ordering::Relaxed);
}
}
}
#[allow(clippy::unnecessary_cast)] // `c_ulong` is `u32` or `u64`
fn load_c_ulong(obj: &PyObject, offset: isize, readonly: bool) -> core::ffi::c_ulong {
let addr = member_addr(obj, offset);
if readonly && size_of::<core::ffi::c_ulong>() == 8 {
// SAFETY: a readonly `unsigned long` member is a `c_ulong` or an
// `AtomicU64` of the same size and alignment. A relaxed atomic load
// reads either; a plain integer is not written after publication, and
// an atomic word (type flags) is updated in place.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicU64>())
.load(core::sync::atomic::Ordering::Relaxed) as core::ffi::c_ulong
}
} else if readonly {
// SAFETY: a readonly `unsigned long` member addresses a `c_ulong` that
// is not written after publication. `c_ulong` is 4 bytes on this target.
unsafe { addr.cast::<core::ffi::c_ulong>().read() }
} else if size_of::<core::ffi::c_ulong>() == 8 {
// SAFETY: a writable `unsigned long` member addresses an aligned `AtomicU64`.
// `c_ulong` is 8 bytes on this target.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicU64>())
.load(core::sync::atomic::Ordering::Relaxed) as core::ffi::c_ulong
}
} else {
// SAFETY: a writable `unsigned long` member addresses an aligned `AtomicU32`.
// `c_ulong` is 4 bytes on this target.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicU32>())
.load(core::sync::atomic::Ordering::Relaxed) as core::ffi::c_ulong
}
}
}
#[allow(clippy::unnecessary_cast)] // `c_ulong` is `u32` or `u64`
fn store_c_ulong(obj: &PyObject, offset: isize, value: core::ffi::c_ulong) {
let addr = member_addr(obj, offset);
if size_of::<core::ffi::c_ulong>() == 8 {
// SAFETY: a writable `unsigned long` member addresses an aligned `AtomicU64`.
// `c_ulong` is 8 bytes on this target.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicU64>())
.store(value as u64, core::sync::atomic::Ordering::Relaxed);
}
} else {
// SAFETY: a writable `unsigned long` member addresses an aligned `AtomicU32`.
// `c_ulong` is 4 bytes on this target.
unsafe {
(*addr.cast::<core::sync::atomic::AtomicU32>())
.store(value as u32, core::sync::atomic::Ordering::Relaxed);
}
}
}
fn load_u32(obj: &PyObject, offset: isize, readonly: bool) -> u32 {
let addr = member_addr(obj, offset);
if readonly {
@@ -740,13 +1064,16 @@ fn member_get_one(
) -> PyResult {
let value = match member.kind {
MemberKind::Object => obj.get_slot(offset).unwrap_or_else(|| vm.ctx.none()),
MemberKind::ObjectEx => obj.get_slot(offset).ok_or_else(|| {
vm.new_attribute_error(format!(
"'{}' object has no attribute '{}'",
obj.class().fully_qualified_name(vm),
member.name
))
})?,
MemberKind::ObjectEx => match obj.get_slot(offset) {
Some(value) => value,
None => {
return Err(vm.new_attribute_error(format!(
"'{}' object has no attribute '{}'",
obj.class().fully_qualified_name(vm)?,
member.name
)));
}
},
MemberKind::Bool => {
// SAFETY: a bool member addresses an `AtomicBool` or a `bool`
// (one byte, naturally aligned). The macro rejects any other field
@@ -797,6 +1124,56 @@ fn member_get_one(
};
vm.ctx.new_int(raw).into()
}
MemberKind::Short => vm
.ctx
.new_int(load_i16(obj, offset, member.readonly()))
.into(),
MemberKind::UShort => vm
.ctx
.new_int(load_u16(obj, offset, member.readonly()))
.into(),
MemberKind::Long => vm
.ctx
.new_int(load_c_long(obj, offset, member.readonly()))
.into(),
MemberKind::LongLong => vm
.ctx
.new_int(load_i64(obj, offset, member.readonly()))
.into(),
MemberKind::ULong => vm
.ctx
.new_int(load_c_ulong(obj, offset, member.readonly()))
.into(),
MemberKind::ULongLong => vm
.ctx
.new_int(load_u64(obj, offset, member.readonly()))
.into(),
MemberKind::String => {
// SAFETY: a string member addresses a `*const c_char` that is not
// written after publication. Null is `None`.
let ptr = unsafe {
member_addr(obj, offset)
.cast::<*const core::ffi::c_char>()
.read()
};
if ptr.is_null() {
vm.ctx.none()
} else {
// SAFETY: `ptr` is non-null. The slot points at a `CString` the
// owner keeps alive, so the bytes stay NUL-terminated for this read.
let bytes = unsafe { core::ffi::CStr::from_ptr(ptr) }.to_bytes();
let Ok(text) = core::str::from_utf8(bytes) else {
return Err(vm.new_unicode_decode_error(
vm.ctx.new_str("utf-8"),
vm.ctx.new_bytes(bytes.to_vec()),
0,
bytes.len(),
vm.ctx.new_str("invalid UTF-8"),
));
};
vm.ctx.new_str(text).into()
}
}
};
Ok(value)
}
@@ -908,10 +1285,131 @@ fn member_set_one(
.store(stored, core::sync::atomic::Ordering::Relaxed);
}
}
MemberKind::Short => {
let PySetterValue::Assign(value) = value else {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
};
let long_val = member_as_c_long(&value, vm)?;
let stored = long_val as i16;
// SAFETY: a writable short member addresses an aligned `AtomicI16`.
unsafe {
(*member_addr(obj, offset).cast::<core::sync::atomic::AtomicI16>())
.store(stored, core::sync::atomic::Ordering::Relaxed);
}
if long_val > i16::MAX as core::ffi::c_long || long_val < i16::MIN as core::ffi::c_long
{
warn_member(vm, "Truncation of value to short")?;
}
}
MemberKind::UShort => {
let PySetterValue::Assign(value) = value else {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
};
let long_val = member_as_c_long(&value, vm)?;
let stored = long_val as u16;
// SAFETY: a writable unsigned short member addresses an aligned `AtomicU16`.
unsafe {
(*member_addr(obj, offset).cast::<core::sync::atomic::AtomicU16>())
.store(stored, core::sync::atomic::Ordering::Relaxed);
}
if long_val > u16::MAX as core::ffi::c_long || long_val < 0 {
warn_member(vm, "Truncation of value to unsigned short")?;
}
}
MemberKind::Long => {
let PySetterValue::Assign(value) = value else {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
};
let stored = member_as_c_long(&value, vm)?;
store_c_long(obj, offset, stored);
}
MemberKind::LongLong => {
let PySetterValue::Assign(value) = value else {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
};
let stored = member_as_c_longlong(&value, vm)?;
// SAFETY: a writable `long long` member addresses an aligned `AtomicI64`.
unsafe {
(*member_addr(obj, offset).cast::<core::sync::atomic::AtomicI64>())
.store(stored, core::sync::atomic::Ordering::Relaxed);
}
}
MemberKind::ULong => {
let PySetterValue::Assign(value) = value else {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
};
let (stored, warning) = member_ulong_value(&value, vm)?;
store_c_ulong(obj, offset, stored);
if let Some(warning) = warning {
warn_member(vm, warning)?;
}
}
MemberKind::ULongLong => {
let PySetterValue::Assign(value) = value else {
return Err(vm.new_type_error("can't delete numeric/char attribute"));
};
let (stored, warning) = member_ulonglong_value(&value, vm)?;
// SAFETY: a writable `unsigned long long` member addresses an aligned `AtomicU64`.
unsafe {
(*member_addr(obj, offset).cast::<core::sync::atomic::AtomicU64>())
.store(stored, core::sync::atomic::Ordering::Relaxed);
}
if let Some(warning) = warning {
warn_member(vm, warning)?;
}
}
MemberKind::String => {
return Err(vm.new_type_error("readonly attribute"));
}
}
Ok(())
}
fn member_as_c_longlong(value: &PyObject, vm: &VirtualMachine) -> PyResult<i64> {
let int_obj = value.try_index(vm)?;
i64::try_from(int_obj.as_bigint())
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C long long"))
}
fn member_ulong_value(
value: &PyObject,
vm: &VirtualMachine,
) -> PyResult<(core::ffi::c_ulong, Option<&'static str>)> {
let int_obj = value.try_index(vm)?;
let big = int_obj.as_bigint();
if big.sign() == malachite_bigint::Sign::Minus {
let long_val = core::ffi::c_long::try_from(big)
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C long"))?;
return Ok((
long_val as core::ffi::c_ulong,
Some("Writing negative value into unsigned field"),
));
}
let stored = core::ffi::c_ulong::try_from(big)
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C unsigned long"))?;
Ok((stored, None))
}
fn member_ulonglong_value(
value: &PyObject,
vm: &VirtualMachine,
) -> PyResult<(u64, Option<&'static str>)> {
let int_obj = value.try_index(vm)?;
let big = int_obj.as_bigint();
if big.sign() == malachite_bigint::Sign::Minus {
let long_val = core::ffi::c_long::try_from(big)
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C long"))?;
return Ok((
long_val as u64,
Some("Writing negative value into unsigned field"),
));
}
let stored = u64::try_from(big).map_err(|_| {
vm.new_overflow_error("Python int too large to convert to C unsigned long long")
})?;
Ok((stored, None))
}
impl Representable for PyMemberDescriptor {
#[inline]
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
+5 -1
View File
@@ -465,7 +465,11 @@ impl FrameObject {
}
}
#[pyclass(flags(DISALLOW_INSTANTIATION), with(Py, Representable))]
#[pyclass(
itemsize = core::mem::size_of::<crate::PyObjectRef>(),
flags(DISALLOW_INSTANTIATION),
with(Py, Representable)
)]
impl FrameObject {
#[pygetset]
pub fn f_globals(&self) -> PyDictRef {
+1
View File
@@ -39,6 +39,7 @@ impl PyPayload for PyGenerator {
}
#[pyclass(
itemsize = core::mem::size_of::<crate::PyObjectRef>(),
flags(DISALLOW_INSTANTIATION, HAS_WEAKREF),
with(Py, IterNext, Iterable, Representable, Destructor)
)]
+3 -3
View File
@@ -562,11 +562,11 @@ pub(crate) fn subs_parameters(
};
if unpack {
let arg_type = arg.class().fully_qualified_name(vm)?;
let substituted_type = substituted_arg.class().fully_qualified_name(vm)?;
let tuple = substituted_arg.try_to_ref::<PyTuple>(vm).map_err(|_| {
vm.new_type_error(format!(
"expected __typing_subst__ of {} objects to return a tuple, not {}",
arg.class().fully_qualified_name(vm),
substituted_arg.class().fully_qualified_name(vm),
"expected __typing_subst__ of {arg_type} objects to return a tuple, not {substituted_type}",
))
})?;
for elem in tuple {
+1
View File
@@ -675,6 +675,7 @@ impl Py<PyMemoryView> {
}
#[pyclass(
itemsize = core::mem::size_of::<isize>(),
with(
Py,
Hashable,
+3
View File
@@ -12,6 +12,9 @@ use crate::{
use rustpython_common::wtf8::Wtf8Buf;
#[pyclass(module = "types", name = "SimpleNamespace")]
// The dict lives in the object extension, not the payload. The offset is
// the dict cell at the front of that extension.
#[pymember(name = "__dict__", offset = ::rustpython_vm::object::dict_member_offset())]
#[derive(Debug, Default)]
pub struct PyNamespace {}
+12 -6
View File
@@ -212,8 +212,8 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine)
if required {
// Dict, weakref list, and slot cells sit in the prefix in front of
// the payload, so they are not part of `slots.basicsize`. Only state
// stored inside the payload counts.
// the object header. `slots.basicsize` is the header plus the payload,
// so only state stored inside the payload counts.
let basicsize = vm.ctx.types.object_type.slots().basicsize;
if obj.class().slots().basicsize > basicsize {
return Err(vm.new_type_error(format!("cannot pickle '{}' object", obj.class().name())));
@@ -343,14 +343,16 @@ impl PyBaseObject {
#[pyslot]
fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
let class = zelf.class();
// A missing `__module__` is not an error here.
let module = class.__module__(vm).ok();
match (
class
.__qualname__(vm)
.downcast_ref::<PyStr>()
.map(|n| n.as_wtf8()),
class
.__module__(vm)
.downcast_ref::<PyStr>()
module
.as_ref()
.and_then(|m| m.downcast_ref::<PyStr>())
.map(|m| m.as_wtf8()),
) {
(None, _) => Err(vm.new_type_error("Unknown qualified name")),
@@ -506,7 +508,11 @@ impl PyBaseObject {
#[pymethod]
fn __sizeof__(zelf: PyObjectRef) -> usize {
zelf.class().slots().basicsize
// `slots.basicsize` includes the object header. This reports the payload.
zelf.class()
.slots()
.basicsize
.saturating_sub(crate::object::SIZEOF_PYOBJECT_HEAD)
}
}
+2 -4
View File
@@ -198,15 +198,13 @@ impl Representable for PyStaticMethod {
let callable = zelf.callable.load_owned().repr(vm)?;
let class = Self::class(&vm.ctx);
let module = class.__module__(vm)?;
match (
class
.__qualname__(vm)
.downcast_ref::<PyStr>()
.map(|n| n.as_wtf8()),
class
.__module__(vm)
.downcast_ref::<PyStr>()
.map(|m| m.as_wtf8()),
module.downcast_ref::<PyStr>().map(|m| m.as_wtf8()),
) {
(None, _) => Err(vm.new_type_error("Unknown qualified name")),
(Some(qualname), Some(module)) if module != "builtins" => {
+149 -126
View File
@@ -9,32 +9,52 @@ use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::function::PyCell,
class::PyClassImpl,
common::lock::PyRwLock,
function::{FuncArgs, IntoFuncArgs, OptionalArg},
object::PyAtomicRef,
types::{Callable, Constructor, GetAttr, GetDescriptor, Initializer, Representable},
};
#[pyclass(module = false, name = "super", traverse)]
#[derive(Debug)]
pub struct PySuper {
inner: PyRwLock<PySuperInner>,
#[pymember(name = "__thisclass__")]
typ: PyAtomicRef<Option<PyType>>,
#[pymember(name = "__self__")]
obj: PyAtomicRef<Option<PyObject>>,
#[pymember(name = "__self_class__")]
obj_type: PyAtomicRef<Option<PyType>>,
}
#[derive(Debug, Traverse)]
struct PySuperInner {
fn bind_super(
typ: PyTypeRef,
obj: Option<(PyObjectRef, PyTypeRef)>,
obj: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<(PyTypeRef, Option<PyObjectRef>, Option<PyTypeRef>)> {
if vm.is_none(&obj) {
return Ok((typ, None, None));
}
let obj_type = super_check(&typ, &obj, vm)?;
Ok((typ, Some(obj), Some(obj_type)))
}
impl PySuperInner {
fn new(typ: PyTypeRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> {
let obj = if vm.is_none(&obj) {
None
} else {
let obj_type = super_check(&typ, &obj, vm)?;
Some((obj, obj_type))
};
Ok(Self { typ, obj })
impl PySuper {
fn empty() -> Self {
Self {
typ: PyAtomicRef::from(None),
obj: PyAtomicRef::from(None),
obj_type: PyAtomicRef::from(None),
}
}
fn store_bound(
&self,
typ: Option<PyTypeRef>,
obj: Option<PyObjectRef>,
obj_type: Option<PyTypeRef>,
) {
drop(self.typ.store(typ));
drop(self.obj.store(obj));
drop(self.obj_type.store(obj_type));
}
}
@@ -48,14 +68,9 @@ impl PyPayload for PySuper {
impl Constructor for PySuper {
type Args = FuncArgs;
fn py_new(_cls: &Py<PyType>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self {
inner: PyRwLock::new(PySuperInner::new(
vm.ctx.types.object_type.to_owned(), // is this correct?
vm.ctx.none(),
vm,
)?),
})
fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
// `tp_new` leaves every field null. `tp_init` fills them in.
Ok(Self::empty())
}
}
@@ -80,106 +95,109 @@ impl Initializer for PySuper {
Self::Args { py_type, object }: Self::Args,
vm: &VirtualMachine,
) -> PyResult<()> {
// Get the type:
let (typ, obj) = if let OptionalArg::Present(ty) = py_type {
(ty, object.unwrap_or_none(vm))
} else {
// Access the InterpreterFrame directly — no need to materialize
// a FrameObject just to read code/locals.
let iframe_ptr = crate::vm::thread::get_current_frame();
if iframe_ptr.is_null() {
return Err(vm.new_runtime_error("super(): no current frame"));
}
let iframe = unsafe { &*iframe_ptr };
let code = iframe.code();
if code.arg_count == 0 {
return Err(vm.new_runtime_error("super(): no arguments"));
}
// SAFETY: InterpreterFrame is current and not concurrently mutated.
use rustpython_compiler_core::bytecode::CO_FAST_CELL;
let fastlocals = iframe.localsplus.fastlocals();
let obj = fastlocals[0]
.clone()
.and_then(|val| {
// If slot 0 is a merged cell (LOCAL|CELL), extract value from cell
if code
.localspluskinds
.first()
.is_some_and(|&k| k & CO_FAST_CELL != 0)
{
val.downcast_ref::<PyCell>().and_then(|c| c.get())
} else {
Some(val)
}
})
.ok_or_else(|| vm.new_runtime_error("super(): arg[0] deleted"))?;
let mut typ = None;
// Search for __class__ in freevars using localspluskinds
let nlocalsplus = code.localspluskinds.len();
let nfrees = code.freevars.len();
let free_start = nlocalsplus - nfrees;
for (i, var) in code.freevars.iter().enumerate() {
if var.as_bytes() == b"__class__" {
let class = fastlocals[free_start + i]
.as_ref()
.and_then(|v| v.downcast_ref::<PyCell>())
.and_then(|c| c.get())
.ok_or_else(|| vm.new_runtime_error("super(): empty __class__ cell"))?;
typ = Some(class.downcast().map_err(|o| {
vm.new_type_error(format!(
"super(): __class__ is not a type ({})",
o.class().name()
))
})?);
break;
}
}
let typ = typ.ok_or_else(|| {
vm.new_type_error(
"super must be called with 1 argument or from inside class method",
)
})?;
(typ, obj)
let typ = match py_type {
OptionalArg::Present(ty) => Some(ty),
OptionalArg::Missing => None,
};
let inner = PySuperInner::new(typ, obj, vm)?;
*zelf.inner.write() = inner;
Ok(())
let obj = match object {
OptionalArg::Present(obj) => Some(obj),
OptionalArg::Missing => None,
};
super_init_impl(zelf, typ, obj, vm)
}
}
/// `super_init_impl`. A null type takes the zero-argument path.
fn super_init_impl(
zelf: &Py<PySuper>,
typ: Option<PyTypeRef>,
obj: Option<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<()> {
let (typ, obj) = match typ {
Some(typ) => (typ, obj.unwrap_or_else(|| vm.ctx.none())),
None => super_init_without_args(vm)?,
};
let (typ, obj, obj_type) = bind_super(typ, obj, vm)?;
zelf.store_bound(Some(typ), obj, obj_type);
Ok(())
}
fn super_init_without_args(vm: &VirtualMachine) -> PyResult<(PyTypeRef, PyObjectRef)> {
// Access the InterpreterFrame directly — no need to materialize
// a FrameObject just to read code/locals.
let iframe_ptr = crate::vm::thread::get_current_frame();
if iframe_ptr.is_null() {
return Err(vm.new_runtime_error("super(): no current frame"));
}
let iframe = unsafe { &*iframe_ptr };
let code = iframe.code();
if code.arg_count == 0 {
return Err(vm.new_runtime_error("super(): no arguments"));
}
// SAFETY: InterpreterFrame is current and not concurrently mutated.
use rustpython_compiler_core::bytecode::CO_FAST_CELL;
let fastlocals = iframe.localsplus.fastlocals();
let obj = fastlocals[0]
.clone()
.and_then(|val| {
// If slot 0 is a merged cell (LOCAL|CELL), extract value from cell
if code
.localspluskinds
.first()
.is_some_and(|&k| k & CO_FAST_CELL != 0)
{
val.downcast_ref::<PyCell>().and_then(|c| c.get())
} else {
Some(val)
}
})
.ok_or_else(|| vm.new_runtime_error("super(): arg[0] deleted"))?;
let mut typ = None;
// Search for __class__ in freevars using localspluskinds
let nlocalsplus = code.localspluskinds.len();
let nfrees = code.freevars.len();
let free_start = nlocalsplus - nfrees;
for (i, var) in code.freevars.iter().enumerate() {
if var.as_bytes() == b"__class__" {
let class = fastlocals[free_start + i]
.as_ref()
.and_then(|v| v.downcast_ref::<PyCell>())
.and_then(|c| c.get())
.ok_or_else(|| vm.new_runtime_error("super(): empty __class__ cell"))?;
typ = Some(class.downcast().map_err(|o| {
vm.new_type_error(format!(
"super(): __class__ is not a type ({})",
o.class().name()
))
})?);
break;
}
}
let typ = typ.ok_or_else(|| {
vm.new_type_error("super must be called with 1 argument or from inside class method")
})?;
Ok((typ, obj))
}
#[pyclass(
with(GetAttr, GetDescriptor, Constructor, Initializer, Representable),
flags(BASETYPE)
)]
impl PySuper {
#[pygetset]
fn __thisclass__(&self) -> PyTypeRef {
self.inner.read().typ.clone()
}
#[pygetset]
fn __self_class__(&self) -> Option<PyTypeRef> {
Some(self.inner.read().obj.as_ref()?.1.clone())
}
#[pygetset]
fn __self__(&self) -> Option<PyObjectRef> {
Some(self.inner.read().obj.as_ref()?.0.clone())
}
}
impl PySuper {}
impl GetAttr for PySuper {
fn getattro(zelf: &Py<Self>, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
let skip = |zelf: &Py<Self>, name| zelf.as_object().generic_getattr(name, vm);
let (obj, start_type): (PyObjectRef, PyTypeRef) = match &zelf.inner.read().obj {
Some(o) => o.clone(),
None => return skip(zelf, name),
let Some(obj) = zelf.obj.load_owned() else {
return skip(zelf, name);
};
let Some(start_type) = zelf.obj_type.load_owned() else {
return skip(zelf, name);
};
// We want __class__ to return the class of the super object
@@ -195,7 +213,9 @@ impl GetAttr for PySuper {
// Both locks are dropped before any arbitrary Python code
// (the descriptor call below) runs, so they can't be held
// across a call that might re-enter and want them again.
let su_type = zelf.inner.read().typ.clone();
let Some(su_type) = zelf.typ.load_owned() else {
return skip(zelf, name);
};
let descr = {
let mro = start_type.mro.read();
mro.iter()
@@ -230,20 +250,23 @@ impl GetDescriptor for PySuper {
vm: &VirtualMachine,
) -> PyResult {
let (zelf, obj) = Self::_unwrap(zelf_obj, obj, vm)?;
if vm.is_none(obj) || zelf.inner.read().obj.is_some() {
if vm.is_none(obj) || zelf.obj.deref().is_some() {
return Ok(zelf_obj.to_owned());
}
let zelf_class = zelf.as_object().class();
let typ = zelf.typ.load_owned();
if zelf_class.is(vm.ctx.types.super_type) {
let typ = zelf.inner.read().typ.clone();
Ok(Self {
inner: PyRwLock::new(PySuperInner::new(typ, obj.to_owned(), vm)?),
}
.into_ref(&vm.ctx)
.into())
let newobj = Self::empty().into_ref(&vm.ctx);
// A null type takes the zero-argument path inside super_init_impl.
super_init_impl(&newobj, typ, Some(obj.to_owned()), vm)?;
Ok(newobj.into())
} else {
let typ = zelf.inner.read().typ.clone();
PyType::call(zelf.class(), (typ, obj.to_owned()).into_args(vm), vm)
// Call stops at the first null argument.
let args = match typ {
Some(typ) => (typ, obj.to_owned()).into_args(vm),
None => FuncArgs::default(),
};
PyType::call(zelf.class(), args, vm)
}
}
}
@@ -251,12 +274,12 @@ impl GetDescriptor for PySuper {
impl Representable for PySuper {
#[inline]
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
let type_name = zelf.inner.read().typ.name().to_owned();
let obj = zelf.inner.read().obj.clone();
let repr = match obj {
Some((_, ref ty)) => {
format!("<super: <class '{}'>, <{} object>>", type_name, ty.name())
}
let type_name = match zelf.typ.load_owned() {
Some(ty) => ty.name().to_owned(),
None => "NULL".to_owned(),
};
let repr = match zelf.obj_type.load_owned() {
Some(ty) => format!("<super: <class '{}'>, <{} object>>", type_name, ty.name()),
None => format!("<super: <class '{type_name}'>, NULL>"),
};
Ok(repr)
+5 -13
View File
@@ -10,9 +10,11 @@ use rustpython_compiler_core::OneIndexed;
#[derive(Debug)]
pub struct PyTraceback {
pub next: PyMutex<Option<PyTracebackRef>>,
#[pymember(name = "tb_frame", audit_read)]
pub frame: FrameObjectRef,
#[pymember(name = "tb_lasti")]
#[pytraverse(skip)]
pub lasti: u32,
pub lasti: i32,
#[pytraverse(skip)]
pub lineno: OneIndexed,
}
@@ -32,7 +34,7 @@ impl PyTraceback {
pub const fn new(
next: Option<PyRef<Self>>,
frame: FrameObjectRef,
lasti: u32,
lasti: i32,
lineno: OneIndexed,
) -> Self {
Self {
@@ -43,16 +45,6 @@ impl PyTraceback {
}
}
#[pygetset]
fn tb_frame(&self) -> FrameObjectRef {
self.frame.clone()
}
#[pygetset]
const fn tb_lasti(&self) -> u32 {
self.lasti
}
#[pygetset]
const fn tb_lineno(&self) -> usize {
self.lineno.get()
@@ -104,7 +96,7 @@ impl PyTraceback {
}
impl Constructor for PyTraceback {
type Args = (Option<PyRef<Self>>, FrameObjectRef, u32, usize);
type Args = (Option<PyRef<Self>>, FrameObjectRef, i32, usize);
fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
let (next, frame, lasti, lineno) = args;
+57 -96
View File
@@ -15,7 +15,6 @@ use crate::{
},
class::{PyClassDef, PyClassImpl, StaticType},
common::{
ascii,
borrow::BorrowedValue,
lock::{PyRwLock, PyRwLockReadGuard},
},
@@ -37,7 +36,7 @@ use core::{
cell::Cell,
ops::Deref,
pin::Pin,
sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering},
sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering},
};
use indexmap::{IndexMap, map::Entry};
use itertools::Itertools;
@@ -56,11 +55,13 @@ pub struct PyType {
pub mro: PyRwLock<Vec<PyTypeRef>>,
pub subclasses: PyRwLock<Vec<PyRef<PyWeak>>>,
pub attributes: TypeNamespace,
#[pymember(name = "__itemsize__", path = "itemsize")]
#[pymember(name = "__basicsize__", path = "basicsize")]
#[pymember(name = "__flags__", path = "flags")]
pub slots: PyTypeSlots,
pub heaptype_ext: Option<Pin<Box<HeapTypeExt>>>,
/// Type version tag for inline caching. 0 means unassigned/invalidated.
pub tp_version_tag: AtomicU32,
pub abc_tpflags: AtomicU64,
}
/// Monotonic counter for type version tags. Once it reaches `u32::MAX`,
@@ -851,9 +852,7 @@ impl PyType {
// Check each base in order and inherit the first collection flag found
for base in bases {
let base_flags = (base.slots.flags
| PyTypeFlags::from_bits_truncate(base.abc_tpflags.load(Ordering::Acquire)))
& COLLECTION_FLAGS;
let base_flags = base.slots.flags.load() & COLLECTION_FLAGS;
if !base_flags.is_empty() {
slots.flags |= base_flags;
return;
@@ -861,40 +860,21 @@ impl PyType {
}
}
fn inherited_abc_tpflags(bases: &[PyRef<Self>]) -> u64 {
const COLLECTION_FLAGS: PyTypeFlags = PyTypeFlags::from_bits_truncate(
PyTypeFlags::SEQUENCE.bits() | PyTypeFlags::MAPPING.bits(),
);
for base in bases {
let base_flags =
PyTypeFlags::from_bits_truncate(base.abc_tpflags.load(Ordering::Acquire))
& COLLECTION_FLAGS;
if !base_flags.is_empty() {
return base_flags.bits();
}
}
0
}
pub fn has_patma_collection_flag(&self, flag: PyTypeFlags) -> bool {
debug_assert!(matches!(flag, PyTypeFlags::SEQUENCE | PyTypeFlags::MAPPING));
const COLLECTION_FLAGS: PyTypeFlags = PyTypeFlags::from_bits_truncate(
PyTypeFlags::SEQUENCE.bits() | PyTypeFlags::MAPPING.bits(),
);
let slot_flags = self.slots.flags & COLLECTION_FLAGS;
if !slot_flags.is_empty() {
return slot_flags.contains(flag);
}
PyTypeFlags::from_bits_truncate(self.abc_tpflags.load(Ordering::Acquire)).contains(flag)
let slot_flags = self.slots.flags.load() & COLLECTION_FLAGS;
slot_flags.contains(flag)
}
pub fn set_is_abstract(&self, is_abstract: bool) {
const MASK: u64 = PyTypeFlags::IS_ABSTRACT.bits();
let _ = self
.abc_tpflags
.try_update(Ordering::AcqRel, Ordering::Acquire, |old| {
Some(if is_abstract { old | MASK } else { old & !MASK })
});
if is_abstract {
self.slots.flags.set(PyTypeFlags::IS_ABSTRACT);
} else {
self.slots.flags.remove(PyTypeFlags::IS_ABSTRACT);
}
self.modified();
}
@@ -906,13 +886,7 @@ impl PyType {
if flags.is_empty() {
return;
}
let collection_bits = COLLECTION_FLAGS.bits();
let flags_bits = flags.bits();
let _ = self
.abc_tpflags
.try_update(Ordering::AcqRel, Ordering::Acquire, |old| {
Some((old & !collection_bits) | flags_bits)
});
self.slots.flags.replace_masked(COLLECTION_FLAGS, flags);
self.modified();
for weak_ref in self.subclasses.read().iter() {
if let Some(subclass) = weak_ref.upgrade()
@@ -1052,7 +1026,6 @@ impl PyType {
));
}
let inherited_abc_tpflags = Self::inherited_abc_tpflags(bases.as_slice());
let new_type = PyRef::new_ref(
Self {
base: Some(base).into(),
@@ -1063,7 +1036,6 @@ impl PyType {
slots,
heaptype_ext: Some(Pin::new(Box::new(heaptype_ext))),
tp_version_tag: AtomicU32::new(0),
abc_tpflags: AtomicU64::new(inherited_abc_tpflags),
},
metaclass,
None,
@@ -1112,7 +1084,6 @@ impl PyType {
slots.flags |= PyTypeFlags::MANAGED_WEAKREF;
}
let inherited_abc_tpflags = Self::inherited_abc_tpflags(core::slice::from_ref(&base));
let bases =
PyTuple::new_ref_typed_with_type(vec![base.clone()], PyTuple::static_type().to_owned());
let mro = base.mro_map_collect(|x| x.to_owned());
@@ -1127,7 +1098,6 @@ impl PyType {
slots,
heaptype_ext: None,
tp_version_tag: AtomicU32::new(0),
abc_tpflags: AtomicU64::new(inherited_abc_tpflags),
},
metaclass,
None,
@@ -1586,17 +1556,19 @@ impl PyType {
/// The type's fully qualified name, the way the `%T` format code prints it:
/// `module.qualname`, with a `builtins` or `__main__` module left off.
pub fn fully_qualified_name(&self, vm: &VirtualMachine) -> String {
pub fn fully_qualified_name(&self, vm: &VirtualMachine) -> PyResult<String> {
let qualname = self.__qualname__(vm);
let qualname = qualname
.downcast_ref::<PyStr>()
.and_then(|qualname| qualname.to_str())
.map_or_else(|| self.name().to_string(), str::to_owned);
let module = self.__module__(vm);
match module.downcast_ref::<PyStr>().and_then(|m| m.to_str()) {
Some("builtins" | "__main__") | None => qualname,
Some(module) => format!("{module}.{qualname}"),
}
let module = self.__module__(vm)?;
Ok(
match module.downcast_ref::<PyStr>().and_then(|m| m.to_str()) {
Some("builtins" | "__main__") | None => qualname,
Some(module) => format!("{module}.{qualname}"),
},
)
}
pub fn name(&self) -> BorrowedValue<'_, str> {
@@ -1722,6 +1694,7 @@ impl Py<PyType> {
}
#[pyclass(
itemsize = core::mem::size_of::<crate::builtins::descriptor::PyMemberDefLayout>(),
with(
Py,
Constructor,
@@ -1923,12 +1896,6 @@ impl PyType {
result
}
#[pygetset]
fn __flags__(&self) -> u64 {
self.slots.flags.bits()
| (self.abc_tpflags.load(Ordering::Acquire) & PyTypeFlags::IS_ABSTRACT.bits())
}
#[pygetset]
fn __abstractmethods__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
if zelf.is(vm.ctx.types.type_type) {
@@ -1970,16 +1937,6 @@ impl PyType {
Ok(())
}
#[pygetset]
fn __basicsize__(&self) -> usize {
crate::object::SIZEOF_PYOBJECT_HEAD + self.slots.basicsize
}
#[pygetset]
fn __itemsize__(&self) -> usize {
self.slots.itemsize
}
#[pygetset]
pub fn __name__(&self, vm: &VirtualMachine) -> PyStrRef {
self.name_inner(
@@ -2243,20 +2200,22 @@ impl PyType {
}
#[pygetset]
pub fn __module__(&self, vm: &VirtualMachine) -> PyObjectRef {
self.attributes
.get(identifier!(vm, __module__))
// We need to exclude this method from going into recursion:
.filter(|found| !found.fast_isinstance(vm.ctx.types.getset_type))
.unwrap_or_else(|| {
// For non-heap types, extract module from tp_name (e.g. "typing.TypeAliasType" -> "typing")
let slot_name = self.slot_name();
if let Some((module, _)) = slot_name.rsplit_once('.') {
vm.ctx.intern_str(module).to_object()
} else {
vm.ctx.new_str(ascii!("builtins")).into()
}
})
pub fn __module__(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
// Heap types store the module in the type dict. Static types take the
// text before the last `.` of the type name, or `builtins`.
if self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
return self
.attributes
.get(identifier!(vm, __module__))
.ok_or_else(|| vm.new_attribute_error("__module__"));
}
let slot_name = self.slot_name();
let module = if let Some((module, _)) = slot_name.rsplit_once('.') {
vm.ctx.intern_str(module)
} else {
vm.ctx.intern_str("builtins")
};
Ok(module.to_object())
}
#[pygetset(setter)]
@@ -2555,10 +2514,10 @@ impl Constructor for PyType {
if let Some(globals) = crate::frame::current_globals() {
let entry = attributes.entry(identifier!(vm, __module__));
if matches!(entry, Entry::Vacant(_)) {
let module_name =
vm.unwrap_or_none(globals.get_item_opt(identifier!(vm, __name__), vm)?);
entry.or_insert(module_name);
if let Entry::Vacant(entry) = entry
&& let Some(module_name) = globals.get_item_opt(identifier!(vm, __name__), vm)?
{
entry.insert(module_name);
}
}
@@ -2602,14 +2561,10 @@ impl Constructor for PyType {
tuple.try_into_typed(vm)?
};
// Check if base has itemsize > 0 - can't add arbitrary slots to variable-size types
// Types like int, bytes, tuple have itemsize > 0 and don't allow custom slots
// But types like weakref.ref have itemsize = 0 and DO allow slots
let has_custom_slots = slots
.as_slice()
.iter()
.any(|s| !matches!(s.as_bytes(), b"__dict__" | b"__weakref__"));
if has_custom_slots && base.slots.itemsize > 0 {
// Any nonempty __slots__ is rejected when the base has a variable
// item size, including a tuple of only `__dict__` or `__weakref__`.
// Types like weakref.ref have itemsize 0 and do allow slots.
if !slots.as_slice().is_empty() && base.slots.itemsize > 0 {
return Err(vm.new_type_error(format!(
"nonempty __slots__ not supported for subtype of '{}'",
base.name()
@@ -2726,14 +2681,16 @@ impl Constructor for PyType {
// Add HAS_WEAKREF if:
// 1. __slots__ is not defined (automatic weakref support), OR
// 2. __weakref__ is in __slots__
let may_add_weakref = !base.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF);
// A variable-size base does not gain a weakref slot.
let may_add_weakref =
base.slots.itemsize == 0 && !base.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF);
if (heaptype_slots.is_none() && may_add_weakref) || add_weakref {
flags |= PyTypeFlags::HAS_WEAKREF | PyTypeFlags::MANAGED_WEAKREF;
}
let (slots, heaptype_ext) = {
let slots = PyTypeSlots {
flags,
flags: crate::types::PyAtomicTypeFlags::new(flags),
member_count,
itemsize: base.slots.itemsize,
..PyTypeSlots::heap_default()
@@ -3301,8 +3258,12 @@ impl AsNumber for PyType {
impl Representable for PyType {
#[inline]
fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
let module = zelf.__module__(vm);
let module = module.downcast_ref::<PyStr>().map(|m| m.as_wtf8());
// A missing `__module__` is not an error here.
let module = zelf.__module__(vm).ok();
let module = module
.as_ref()
.and_then(|m| m.downcast_ref::<PyStr>())
.map(|m| m.as_wtf8());
let repr = match module {
Some(module) if module != "builtins" => {
@@ -3703,7 +3664,7 @@ fn mro_internal(typ: &Py<PyType>, vm: &VirtualMachine) -> PyResult<i32> {
/// Returns true if the two types have different instance layouts.
fn shape_differs(t1: &Py<PyType>, t2: &Py<PyType>) -> bool {
t1.__basicsize__() != t2.__basicsize__() || t1.slots.itemsize != t2.slots.itemsize
t1.slots.basicsize != t2.slots.basicsize || t1.slots.itemsize != t2.slots.itemsize
}
fn solid_base<'a>(typ: &'a Py<PyType>, vm: &VirtualMachine) -> &'a Py<PyType> {
+11 -3
View File
@@ -145,10 +145,16 @@ pub trait StaticType {
where
Self: PyClassImpl,
{
// inherit_special COPYVAL(tp_itemsize): the direct base, and only when
// this type left the slot at 0. The base type object already exists.
let mut slots = Self::make_slots();
if slots.itemsize == 0 {
slots.itemsize = Self::static_baseclass().slots.itemsize;
}
PyType::new_static(
Self::static_baseclass().to_owned(),
Default::default(),
Self::make_slots(),
slots,
Self::static_metaclass().to_owned(),
)
.unwrap()
@@ -263,7 +269,9 @@ pub trait PyClassImpl: PyClassDef {
let _ = ctx.intern_str(Self::NAME); // intern type name
if Self::TP_FLAGS.has_feature(PyTypeFlags::HAS_DICT) {
if Self::TP_FLAGS.has_feature(PyTypeFlags::HAS_DICT)
&& !Self::MEMBERS.iter().any(|member| member.name == "__dict__")
{
let __dict__ = identifier!(ctx, __dict__);
class.set_attr(
__dict__,
@@ -376,7 +384,7 @@ pub trait PyClassImpl: PyClassDef {
fn make_slots() -> PyTypeSlots {
let mut slots = PyTypeSlots {
flags: Self::TP_FLAGS,
flags: crate::types::PyAtomicTypeFlags::new(Self::TP_FLAGS),
name: Self::TP_NAME,
basicsize: Self::BASICSIZE,
itemsize: Self::ITEMSIZE,
+1 -1
View File
@@ -657,7 +657,7 @@ pub(crate) fn unraisable_while_closing(
if e.traceback().is_none()
&& let Some(frame) = coro.frame_opt()
{
let lasti = frame.lasti().saturating_mul(2);
let lasti = frame.lasti().saturating_mul(2) as i32;
let lineno = rustpython_compiler_core::OneIndexed::new(frame.lineno().max(1) as usize)
.unwrap_or(rustpython_compiler_core::OneIndexed::MIN);
let tb = PyTraceback::new(None, frame, lasti, lineno);
+9 -2
View File
@@ -18,16 +18,23 @@ use crate::exceptions::types::PyBaseException;
fn create_exception_group(ctx: &Context) -> PyRef<PyType> {
let excs = &ctx.exceptions;
let exception_group_slots = PyTypeSlots {
flags: PyTypeFlags::heap_type_flags() | PyTypeFlags::HAS_DICT,
flags: crate::types::PyAtomicTypeFlags::new(
PyTypeFlags::heap_type_flags() | PyTypeFlags::HAS_DICT,
),
..Default::default()
};
let mut attrs = crate::builtins::type_::PyAttributes::default();
attrs.insert(
crate::identifier!(ctx, __module__),
ctx.intern_str("builtins").to_object(),
);
PyType::new_heap(
"ExceptionGroup",
vec![
excs.base_exception_group.to_owned(),
excs.exception_type.to_owned(),
],
Default::default(),
attrs,
exception_group_slots,
ctx.types.type_type.to_owned(),
ctx,
+7 -7
View File
@@ -2689,7 +2689,7 @@ pub(crate) fn trampoline_handle_exception(
// Add traceback entry at the call site.
if let Some((loc, _end_loc)) = exec.code.locations.get(idx) {
let next = exception.traceback();
let new_traceback = PyTraceback::new(next, exec.frame_object(vm), idx as u32 * 2, loc.line);
let new_traceback = PyTraceback::new(next, exec.frame_object(vm), idx as i32 * 2, loc.line);
exception.set_traceback(Some(new_traceback.into_ref(&vm.ctx)));
}
@@ -3608,7 +3608,7 @@ impl ExecutingFrame<'_> {
let new_traceback = PyTraceback::new(
next,
self.frame_object(vm),
idx as u32 * 2,
idx as i32 * 2,
loc.line,
);
exception.set_traceback(Some(new_traceback.into_ref(&vm.ctx)));
@@ -3717,7 +3717,7 @@ impl ExecutingFrame<'_> {
let new_traceback = PyTraceback::new(
next,
frame.frame_object(vm),
idx as u32 * 2,
idx as i32 * 2,
loc.line,
);
exception.set_traceback(Some(new_traceback.into_ref(&vm.ctx)));
@@ -3808,7 +3808,7 @@ impl ExecutingFrame<'_> {
let new_traceback = PyTraceback::new(
next,
frame.frame_object(vm),
idx as u32 * 2,
idx as i32 * 2,
loc.line,
);
vm_trace!(
@@ -4017,7 +4017,7 @@ impl ExecutingFrame<'_> {
let (loc, _end_loc) = self.code.locations[idx];
let next = err.traceback();
let new_traceback =
PyTraceback::new(next, self.frame_object(vm), idx as u32 * 2, loc.line);
PyTraceback::new(next, self.frame_object(vm), idx as i32 * 2, loc.line);
err.set_traceback(Some(new_traceback.into_ref(&vm.ctx)));
}
@@ -4077,7 +4077,7 @@ impl ExecutingFrame<'_> {
let new_traceback = PyTraceback::new(
next,
self.frame_object(vm),
idx as u32 * 2,
idx as i32 * 2,
loc.line,
);
err.set_traceback(Some(new_traceback.into_ref(&vm.ctx)));
@@ -4124,7 +4124,7 @@ impl ExecutingFrame<'_> {
let (loc, _end_loc) = self.code.locations[idx];
let next = exception.traceback();
let new_traceback =
PyTraceback::new(next, self.frame_object(vm), idx as u32 * 2, loc.line);
PyTraceback::new(next, self.frame_object(vm), idx as i32 * 2, loc.line);
exception.set_traceback(Some(new_traceback.into_ref(&vm.ctx)));
}
+4 -4
View File
@@ -158,7 +158,7 @@ impl PyMethodDef {
zelf: None,
value: self,
module_object: None,
module: None,
module: crate::object::PyAtomicRef::new_empty(),
_method_def_owner: None,
}
}
@@ -181,7 +181,7 @@ impl PyMethodDef {
zelf: Some(obj),
value: self,
module_object: None,
module: None,
module: crate::object::PyAtomicRef::new_empty(),
_method_def_owner: None,
},
class,
@@ -201,7 +201,7 @@ impl PyMethodDef {
zelf: Some(obj),
value: self,
module_object: None,
module: None,
module: crate::object::PyAtomicRef::new_empty(),
_method_def_owner: None,
};
PyRef::new_ref(
@@ -255,7 +255,7 @@ impl PyMethodDef {
zelf: Some(class.to_owned().into()),
value: self,
module_object: None,
module: None,
module: crate::object::PyAtomicRef::new_empty(),
_method_def_owner: None,
};
PyNativeMethod { func, class }.into_ref(ctx)
+1 -1
View File
@@ -16,7 +16,7 @@ macro_rules! py_class {
{
#[allow(unused_mut)]
let mut slots = $crate::types::PyTypeSlots::heap_default();
slots.flags = $flags;
slots.flags = $crate::types::PyAtomicTypeFlags::new($flags);
$($crate::py_class!(@extract_slots($ctx, &mut slots, $name, $value));)*
let py_class = $ctx.new_class(None, $class_name, $class_base, slots);
$($crate::py_class!(@extract_attrs($ctx, &py_class, $name, $value));)*
+18 -9
View File
@@ -514,9 +514,17 @@ impl<T> Py<T> {
fn read_type_flags(&self) -> (crate::types::PyTypeFlags, usize) {
let typ_ptr = self.typ.load_raw();
let slots = unsafe { core::ptr::addr_of!((*typ_ptr).payload.slots) };
let flags = unsafe { core::ptr::addr_of!((*slots).flags).read() };
// SAFETY: `flags` is `PyAtomicTypeFlags`, transparent over `AtomicU64`.
// The type object is live. This load does not form a reference to it.
let bits = unsafe {
(*core::ptr::addr_of!((*slots).flags).cast::<core::sync::atomic::AtomicU64>())
.load(core::sync::atomic::Ordering::Acquire)
};
let member_count = unsafe { core::ptr::addr_of!((*slots).member_count).read() };
(flags, member_count)
(
crate::types::PyTypeFlags::from_bits_truncate(bits),
member_count,
)
}
/// Access the ObjExt prefix at a negative offset from this Py.
@@ -1402,9 +1410,14 @@ impl<T: PyPayload + core::fmt::Debug> Py<T> {
if let Some(offset) = ext_start {
let ext_ptr = alloc_ptr.add(offset) as *mut ObjExt;
let flags = typ.slots.flags;
let has_dict = flags.has_feature(crate::types::PyTypeFlags::HAS_DICT);
let inline_values = flags.has_feature(crate::types::PyTypeFlags::INLINE_VALUES);
let has_dict = typ
.slots
.flags
.has_feature(crate::types::PyTypeFlags::HAS_DICT);
let inline_values = typ
.slots
.flags
.has_feature(crate::types::PyTypeFlags::INLINE_VALUES);
ext_ptr.write(ObjExt::new(dict, has_dict, inline_values));
}
@@ -3118,7 +3131,6 @@ pub(crate) fn init_type_hierarchy() -> BootstrapTypeHierarchy {
slots: PyType::make_slots(),
heaptype_ext: None,
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
};
let object_payload = PyType {
base: unsafe { PyAtomicRef::from_optional_ref_without_retag(None) },
@@ -3129,7 +3141,6 @@ pub(crate) fn init_type_hierarchy() -> BootstrapTypeHierarchy {
slots: object::PyBaseObject::make_slots(),
heaptype_ext: None,
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
};
let tuple_payload = PyType {
base: unsafe {
@@ -3144,7 +3155,6 @@ pub(crate) fn init_type_hierarchy() -> BootstrapTypeHierarchy {
slots: tuple::PyTuple::make_slots(),
heaptype_ext: None,
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
};
let object_element =
@@ -3192,7 +3202,6 @@ pub(crate) fn init_type_hierarchy() -> BootstrapTypeHierarchy {
slots: PyWeak::make_slots(),
heaptype_ext: None,
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
};
let weakref_type = PyRef::new_ref(weakref_payload, type_type.clone(), None);
// Static type: untrack from GC (was tracked by new_ref because PyType has HAS_TRAVERSE)
+41 -9
View File
@@ -252,6 +252,7 @@ const _: () = assert!(
== core::mem::align_of::<PyAtomicRef<()>>()
&& core::mem::offset_of!(PyAtomicRef<Option<PyObject>>, inner)
== core::mem::offset_of!(PyAtomicRef<()>, inner)
&& core::mem::offset_of!(PyAtomicRef<Option<PyObject>>, inner) == 0
);
impl<T> Drop for PyAtomicRef<T> {
@@ -367,6 +368,32 @@ impl<T: PyPayload> PyAtomicRef<T> {
frame.iframe().cold().temporary_refs.lock().push(old.into());
}
}
/// Strong reference to the current value.
///
/// The cell is never null. A concurrent store may drop the previous value;
/// the incref is retried until it applies to the pointer still in the slot.
/// A null load is retried rather than forged.
pub(crate) fn load_owned(&self) -> PyRef<T> {
loop {
if let Some(obj) = cell_load_owned(&self.inner) {
// SAFETY: this cell is only stored with `PyRef<T>`.
return unsafe { obj.downcast_unchecked() };
}
core::hint::spin_loop();
}
}
/// Replace the stored reference. Returns the previous one, still owned.
///
/// `None` only if the cell was empty. Callers that publish through
/// `From<PyRef<T>>` and `store` keep a `T` in the slot.
pub(crate) fn store(&self, value: PyRef<T>) -> Option<PyRef<T>> {
cell_store(&self.inner, Some(value.into())).map(|obj| {
// SAFETY: this cell is only stored with `PyRef<T>`.
unsafe { obj.downcast_unchecked() }
})
}
}
impl<T: PyPayload> From<Option<PyRef<T>>> for PyAtomicRef<Option<T>> {
@@ -460,13 +487,6 @@ impl<T: PyPayload> PyAtomicRef<Option<T>> {
}
}
fn empty_cell<T>() -> PyAtomicRef<T> {
PyAtomicRef {
inner: Radium::new(null_mut()),
_phantom: PhantomData,
}
}
fn cell_load_ptr(inner: &PyAtomic<*mut u8>) -> *mut PyObject {
inner.load(Ordering::Acquire).cast()
}
@@ -629,8 +649,20 @@ impl From<Option<PyObjectRef>> for PyAtomicRef<Option<PyObject>> {
impl PyAtomicRef<Option<PyObject>> {
/// Empty slot. The pointer is null and owns no reference.
pub(crate) fn new_empty() -> Self {
empty_cell()
pub(crate) const fn new_empty() -> Self {
Self {
inner: {
#[cfg(feature = "threading")]
{
core::sync::atomic::AtomicPtr::new(null_mut())
}
#[cfg(not(feature = "threading"))]
{
core::cell::Cell::new(null_mut())
}
},
_phantom: PhantomData,
}
}
/// Borrowed pointer currently stored. Null when the slot is empty.
+7
View File
@@ -125,6 +125,13 @@ unsafe impl<T: crate::PyPayload> Traverse for super::ext::PyAtomicRef<Option<T>>
}
}
unsafe impl<T: crate::PyPayload> Traverse for super::ext::PyAtomicRef<T> {
#[inline]
fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
traverse_fn(self.as_object());
}
}
unsafe impl Traverse for super::ext::PyAtomicRef<PyObject> {
#[inline]
fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
+1 -3
View File
@@ -252,9 +252,7 @@ mod _abc {
// _abc_register propagates Py_TPFLAGS_SEQUENCE/MAPPING
// recursively so MATCH_SEQUENCE/MATCH_MAPPING see ABC registration.
let collection_mask = PyTypeFlags::SEQUENCE | PyTypeFlags::MAPPING;
let collection_flags = (cls_type.slots.flags
| PyTypeFlags::from_bits_truncate(cls_type.abc_tpflags.load(Ordering::Acquire)))
& collection_mask;
let collection_flags = cls_type.slots.flags.load() & collection_mask;
if !subclass_type.is(vm.ctx.types.str_type)
&& !subclass_type.is(vm.ctx.types.bytes_type)
&& !subclass_type.is(vm.ctx.types.bytearray_type)
+134 -146
View File
@@ -83,13 +83,8 @@ macro_rules! impl_base_node {
#[extend_class]
fn extend_class(ctx: &Context, class: &'static Py<PyType>) {
// AST types are mutable (heap types in CPython, not IMMUTABLETYPE)
// Safety: called during type initialization before any concurrent access
unsafe {
let flags = &class.slots.flags as *const crate::types::PyTypeFlags
as *mut crate::types::PyTypeFlags;
(*flags).remove(crate::types::PyTypeFlags::IMMUTABLETYPE);
}
// AST types are mutable (heap types, not IMMUTABLETYPE).
class.slots.flags.remove(crate::types::PyTypeFlags::IMMUTABLETYPE);
class.set_attr(
identifier!(ctx, _fields),
ctx.empty_tuple.clone().into(),
@@ -132,13 +127,8 @@ macro_rules! impl_base_node {
#[extend_class]
fn extend_class_with_fields(ctx: &Context, class: &'static Py<PyType>) {
// AST types are mutable (heap types in CPython, not IMMUTABLETYPE)
// Safety: called during type initialization before any concurrent access
unsafe {
let flags = &class.slots.flags as *const crate::types::PyTypeFlags
as *mut crate::types::PyTypeFlags;
(*flags).remove(crate::types::PyTypeFlags::IMMUTABLETYPE);
}
// AST types are mutable (heap types, not IMMUTABLETYPE).
class.slots.flags.remove(crate::types::PyTypeFlags::IMMUTABLETYPE);
class.set_attr(
identifier!(ctx, _fields),
ctx.new_tuple(vec![
@@ -179,30 +169,30 @@ macro_rules! impl_base_node {
};
}
#[pyclass(module = "_ast", name = "mod", base = NodeAst)]
#[pyclass(module = "ast", name = "mod", base = NodeAst)]
pub(crate) struct NodeMod(NodeAst);
impl_base_node!(NodeMod);
impl_node!(
#[pyclass(module = "_ast", name = "Module", base = NodeMod)]
#[pyclass(module = "ast", name = "Module", base = NodeMod)]
pub(crate) struct NodeModModule,
fields: ["body", "type_ignores"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Interactive", base = NodeMod)]
#[pyclass(module = "ast", name = "Interactive", base = NodeMod)]
pub(crate) struct NodeModInteractive,
fields: ["body"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Expression", base = NodeMod)]
#[pyclass(module = "ast", name = "Expression", base = NodeMod)]
pub(crate) struct NodeModExpression,
fields: ["body"],
);
#[pyclass(module = "_ast", name = "stmt", base = NodeAst)]
#[pyclass(module = "ast", name = "stmt", base = NodeAst)]
#[repr(transparent)]
pub(crate) struct NodeStmt(NodeAst);
@@ -213,199 +203,199 @@ impl_base_node!(
);
impl_node!(
#[pyclass(module = "_ast", name = "FunctionType", base = NodeMod)]
#[pyclass(module = "ast", name = "FunctionType", base = NodeMod)]
pub(crate) struct NodeModFunctionType,
fields: ["argtypes", "returns"],
);
impl_node!(
#[pyclass(module = "_ast", name = "FunctionDef", base = NodeStmt)]
#[pyclass(module = "ast", name = "FunctionDef", base = NodeStmt)]
pub(crate) struct NodeStmtFunctionDef,
fields: ["name", "args", "body", "decorator_list", "returns", "type_comment", "type_params"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "AsyncFunctionDef", base = NodeStmt)]
#[pyclass(module = "ast", name = "AsyncFunctionDef", base = NodeStmt)]
pub(crate) struct NodeStmtAsyncFunctionDef,
fields: ["name", "args", "body", "decorator_list", "returns", "type_comment", "type_params"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "ClassDef", base = NodeStmt)]
#[pyclass(module = "ast", name = "ClassDef", base = NodeStmt)]
pub(crate) struct NodeStmtClassDef,
fields: ["name", "bases", "keywords", "body", "decorator_list", "type_params"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Return", base = NodeStmt)]
#[pyclass(module = "ast", name = "Return", base = NodeStmt)]
pub(crate) struct NodeStmtReturn,
fields: ["value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Delete", base = NodeStmt)]
#[pyclass(module = "ast", name = "Delete", base = NodeStmt)]
pub(crate) struct NodeStmtDelete,
fields: ["targets"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Assign", base = NodeStmt)]
#[pyclass(module = "ast", name = "Assign", base = NodeStmt)]
pub(crate) struct NodeStmtAssign,
fields: ["targets", "value", "type_comment"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "TypeAlias", base = NodeStmt)]
#[pyclass(module = "ast", name = "TypeAlias", base = NodeStmt)]
pub(crate) struct NodeStmtTypeAlias,
fields: ["name", "type_params", "value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "AugAssign", base = NodeStmt)]
#[pyclass(module = "ast", name = "AugAssign", base = NodeStmt)]
pub(crate) struct NodeStmtAugAssign,
fields: ["target", "op", "value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "AnnAssign", base = NodeStmt)]
#[pyclass(module = "ast", name = "AnnAssign", base = NodeStmt)]
pub(crate) struct NodeStmtAnnAssign,
fields: ["target", "annotation", "value", "simple"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "For", base = NodeStmt)]
#[pyclass(module = "ast", name = "For", base = NodeStmt)]
pub(crate) struct NodeStmtFor,
fields: ["target", "iter", "body", "orelse", "type_comment"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "AsyncFor", base = NodeStmt)]
#[pyclass(module = "ast", name = "AsyncFor", base = NodeStmt)]
pub(crate) struct NodeStmtAsyncFor,
fields: ["target", "iter", "body", "orelse", "type_comment"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "While", base = NodeStmt)]
#[pyclass(module = "ast", name = "While", base = NodeStmt)]
pub(crate) struct NodeStmtWhile,
fields: ["test", "body", "orelse"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "If", base = NodeStmt)]
#[pyclass(module = "ast", name = "If", base = NodeStmt)]
pub(crate) struct NodeStmtIf,
fields: ["test", "body", "orelse"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "With", base = NodeStmt)]
#[pyclass(module = "ast", name = "With", base = NodeStmt)]
pub(crate) struct NodeStmtWith,
fields: ["items", "body", "type_comment"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "AsyncWith", base = NodeStmt)]
#[pyclass(module = "ast", name = "AsyncWith", base = NodeStmt)]
pub(crate) struct NodeStmtAsyncWith,
fields: ["items", "body", "type_comment"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Match", base = NodeStmt)]
#[pyclass(module = "ast", name = "Match", base = NodeStmt)]
pub(crate) struct NodeStmtMatch,
fields: ["subject", "cases"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Raise", base = NodeStmt)]
#[pyclass(module = "ast", name = "Raise", base = NodeStmt)]
pub(crate) struct NodeStmtRaise,
fields: ["exc", "cause"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Try", base = NodeStmt)]
#[pyclass(module = "ast", name = "Try", base = NodeStmt)]
pub(crate) struct NodeStmtTry,
fields: ["body", "handlers", "orelse", "finalbody"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "TryStar", base = NodeStmt)]
#[pyclass(module = "ast", name = "TryStar", base = NodeStmt)]
pub(crate) struct NodeStmtTryStar,
fields: ["body", "handlers", "orelse", "finalbody"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Assert", base = NodeStmt)]
#[pyclass(module = "ast", name = "Assert", base = NodeStmt)]
pub(crate) struct NodeStmtAssert,
fields: ["test", "msg"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Import", base = NodeStmt)]
#[pyclass(module = "ast", name = "Import", base = NodeStmt)]
pub(crate) struct NodeStmtImport,
fields: ["names"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "ImportFrom", base = NodeStmt)]
#[pyclass(module = "ast", name = "ImportFrom", base = NodeStmt)]
pub(crate) struct NodeStmtImportFrom,
fields: ["module", "names", "level"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Global", base = NodeStmt)]
#[pyclass(module = "ast", name = "Global", base = NodeStmt)]
pub(crate) struct NodeStmtGlobal,
fields: ["names"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Nonlocal", base = NodeStmt)]
#[pyclass(module = "ast", name = "Nonlocal", base = NodeStmt)]
pub(crate) struct NodeStmtNonlocal,
fields: ["names"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Expr", base = NodeStmt)]
#[pyclass(module = "ast", name = "Expr", base = NodeStmt)]
pub(crate) struct NodeStmtExpr,
fields: ["value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Pass", base = NodeStmt)]
#[pyclass(module = "ast", name = "Pass", base = NodeStmt)]
pub(crate) struct NodeStmtPass,
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Break", base = NodeStmt)]
#[pyclass(module = "ast", name = "Break", base = NodeStmt)]
pub(crate) struct NodeStmtBreak,
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
#[pyclass(module = "_ast", name = "expr", base = NodeAst)]
#[pyclass(module = "ast", name = "expr", base = NodeAst)]
#[repr(transparent)]
pub(crate) struct NodeExpr(NodeAst);
@@ -416,159 +406,159 @@ impl_base_node!(
);
impl_node!(
#[pyclass(module = "_ast", name = "Continue", base = NodeStmt)]
#[pyclass(module = "ast", name = "Continue", base = NodeStmt)]
pub(crate) struct NodeStmtContinue,
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "BoolOp", base = NodeExpr)]
#[pyclass(module = "ast", name = "BoolOp", base = NodeExpr)]
pub(crate) struct NodeExprBoolOp,
fields: ["op", "values"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "NamedExpr", base = NodeExpr)]
#[pyclass(module = "ast", name = "NamedExpr", base = NodeExpr)]
pub(crate) struct NodeExprNamedExpr,
fields: ["target", "value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "BinOp", base = NodeExpr)]
#[pyclass(module = "ast", name = "BinOp", base = NodeExpr)]
pub(crate) struct NodeExprBinOp,
fields: ["left", "op", "right"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "UnaryOp", base = NodeExpr)]
#[pyclass(module = "ast", name = "UnaryOp", base = NodeExpr)]
pub(crate) struct NodeExprUnaryOp,
fields: ["op", "operand"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Lambda", base = NodeExpr)]
#[pyclass(module = "ast", name = "Lambda", base = NodeExpr)]
pub(crate) struct NodeExprLambda,
fields: ["args", "body"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "IfExp", base = NodeExpr)]
#[pyclass(module = "ast", name = "IfExp", base = NodeExpr)]
pub(crate) struct NodeExprIfExp,
fields: ["test", "body", "orelse"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Dict", base = NodeExpr)]
#[pyclass(module = "ast", name = "Dict", base = NodeExpr)]
pub(crate) struct NodeExprDict,
fields: ["keys", "values"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Set", base = NodeExpr)]
#[pyclass(module = "ast", name = "Set", base = NodeExpr)]
pub(crate) struct NodeExprSet,
fields: ["elts"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "ListComp", base = NodeExpr)]
#[pyclass(module = "ast", name = "ListComp", base = NodeExpr)]
pub(crate) struct NodeExprListComp,
fields: ["elt", "generators"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "SetComp", base = NodeExpr)]
#[pyclass(module = "ast", name = "SetComp", base = NodeExpr)]
pub(crate) struct NodeExprSetComp,
fields: ["elt", "generators"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "DictComp", base = NodeExpr)]
#[pyclass(module = "ast", name = "DictComp", base = NodeExpr)]
pub(crate) struct NodeExprDictComp,
fields: ["key", "value", "generators"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "GeneratorExp", base = NodeExpr)]
#[pyclass(module = "ast", name = "GeneratorExp", base = NodeExpr)]
pub(crate) struct NodeExprGeneratorExp,
fields: ["elt", "generators"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Await", base = NodeExpr)]
#[pyclass(module = "ast", name = "Await", base = NodeExpr)]
pub(crate) struct NodeExprAwait,
fields: ["value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Yield", base = NodeExpr)]
#[pyclass(module = "ast", name = "Yield", base = NodeExpr)]
pub(crate) struct NodeExprYield,
fields: ["value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "YieldFrom", base = NodeExpr)]
#[pyclass(module = "ast", name = "YieldFrom", base = NodeExpr)]
pub(crate) struct NodeExprYieldFrom,
fields: ["value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Compare", base = NodeExpr)]
#[pyclass(module = "ast", name = "Compare", base = NodeExpr)]
pub(crate) struct NodeExprCompare,
fields: ["left", "ops", "comparators"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Call", base = NodeExpr)]
#[pyclass(module = "ast", name = "Call", base = NodeExpr)]
pub(crate) struct NodeExprCall,
fields: ["func", "args", "keywords"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "FormattedValue", base = NodeExpr)]
#[pyclass(module = "ast", name = "FormattedValue", base = NodeExpr)]
pub(crate) struct NodeExprFormattedValue,
fields: ["value", "conversion", "format_spec"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "JoinedStr", base = NodeExpr)]
#[pyclass(module = "ast", name = "JoinedStr", base = NodeExpr)]
pub(crate) struct NodeExprJoinedStr,
fields: ["values"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "TemplateStr", base = NodeExpr)]
#[pyclass(module = "ast", name = "TemplateStr", base = NodeExpr)]
pub(crate) struct NodeExprTemplateStr,
fields: ["values"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Interpolation", base = NodeExpr)]
#[pyclass(module = "ast", name = "Interpolation", base = NodeExpr)]
pub(crate) struct NodeExprInterpolation,
fields: ["value", "str", "conversion", "format_spec"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
#[pyclass(module = "_ast", name = "Constant", base = NodeExpr)]
#[pyclass(module = "ast", name = "Constant", base = NodeExpr)]
#[repr(transparent)]
pub(crate) struct NodeExprConstant(NodeExpr);
@@ -576,13 +566,11 @@ pub(crate) struct NodeExprConstant(NodeExpr);
impl NodeExprConstant {
#[extend_class]
fn extend_class_with_fields(ctx: &Context, class: &'static Py<PyType>) {
// AST types are mutable (heap types, not IMMUTABLETYPE)
// Safety: called during type initialization before any concurrent access
unsafe {
let flags = &class.slots.flags as *const crate::types::PyTypeFlags
as *mut crate::types::PyTypeFlags;
(*flags).remove(crate::types::PyTypeFlags::IMMUTABLETYPE);
}
// AST types are mutable (heap types, not IMMUTABLETYPE).
class
.slots
.flags
.remove(crate::types::PyTypeFlags::IMMUTABLETYPE);
class.set_attr(
identifier!(ctx, _fields),
ctx.new_tuple(vec![
@@ -615,245 +603,245 @@ impl NodeExprConstant {
}
impl_node!(
#[pyclass(module = "_ast", name = "Attribute", base = NodeExpr)]
#[pyclass(module = "ast", name = "Attribute", base = NodeExpr)]
pub(crate) struct NodeExprAttribute,
fields: ["value", "attr", "ctx"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Subscript", base = NodeExpr)]
#[pyclass(module = "ast", name = "Subscript", base = NodeExpr)]
pub(crate) struct NodeExprSubscript,
fields: ["value", "slice", "ctx"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Starred", base = NodeExpr)]
#[pyclass(module = "ast", name = "Starred", base = NodeExpr)]
pub(crate) struct NodeExprStarred,
fields: ["value", "ctx"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Name", base = NodeExpr)]
#[pyclass(module = "ast", name = "Name", base = NodeExpr)]
pub(crate) struct NodeExprName,
fields: ["id", "ctx"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "List", base = NodeExpr)]
#[pyclass(module = "ast", name = "List", base = NodeExpr)]
pub(crate) struct NodeExprList,
fields: ["elts", "ctx"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Tuple", base = NodeExpr)]
#[pyclass(module = "ast", name = "Tuple", base = NodeExpr)]
pub(crate) struct NodeExprTuple,
fields: ["elts", "ctx"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
#[pyclass(module = "_ast", name = "expr_context", base = NodeAst)]
#[pyclass(module = "ast", name = "expr_context", base = NodeAst)]
#[repr(transparent)]
pub(crate) struct NodeExprContext(NodeAst);
impl_base_node!(NodeExprContext);
impl_node!(
#[pyclass(module = "_ast", name = "Slice", base = NodeExpr)]
#[pyclass(module = "ast", name = "Slice", base = NodeExpr)]
pub(crate) struct NodeExprSlice,
fields: ["lower", "upper", "step"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "Load", base = NodeExprContext)]
#[pyclass(module = "ast", name = "Load", base = NodeExprContext)]
pub(crate) struct NodeExprContextLoad,
);
impl_node!(
#[pyclass(module = "_ast", name = "Store", base = NodeExprContext)]
#[pyclass(module = "ast", name = "Store", base = NodeExprContext)]
pub(crate) struct NodeExprContextStore,
);
#[pyclass(module = "_ast", name = "boolop", base = NodeAst)]
#[pyclass(module = "ast", name = "boolop", base = NodeAst)]
#[repr(transparent)]
pub(crate) struct NodeBoolOp(NodeAst);
impl_base_node!(NodeBoolOp);
impl_node!(
#[pyclass(module = "_ast", name = "Del", base = NodeExprContext)]
#[pyclass(module = "ast", name = "Del", base = NodeExprContext)]
pub(crate) struct NodeExprContextDel,
);
impl_node!(
#[pyclass(module = "_ast", name = "And", base = NodeBoolOp)]
#[pyclass(module = "ast", name = "And", base = NodeBoolOp)]
pub(crate) struct NodeBoolOpAnd,
);
#[pyclass(module = "_ast", name = "operator", base = NodeAst)]
#[pyclass(module = "ast", name = "operator", base = NodeAst)]
#[repr(transparent)]
pub(crate) struct NodeOperator(NodeAst);
impl_base_node!(NodeOperator);
impl_node!(
#[pyclass(module = "_ast", name = "Or", base = NodeBoolOp)]
#[pyclass(module = "ast", name = "Or", base = NodeBoolOp)]
pub(crate) struct NodeBoolOpOr,
);
impl_node!(
#[pyclass(module = "_ast", name = "Add", base = NodeOperator)]
#[pyclass(module = "ast", name = "Add", base = NodeOperator)]
pub(crate) struct NodeOperatorAdd,
);
impl_node!(
#[pyclass(module = "_ast", name = "Sub", base = NodeOperator)]
#[pyclass(module = "ast", name = "Sub", base = NodeOperator)]
pub(crate) struct NodeOperatorSub,
);
impl_node!(
#[pyclass(module = "_ast", name = "Mult", base = NodeOperator)]
#[pyclass(module = "ast", name = "Mult", base = NodeOperator)]
pub(crate) struct NodeOperatorMult,
);
impl_node!(
#[pyclass(module = "_ast", name = "MatMult", base = NodeOperator)]
#[pyclass(module = "ast", name = "MatMult", base = NodeOperator)]
pub(crate) struct NodeOperatorMatMult,
);
impl_node!(
#[pyclass(module = "_ast", name = "Div", base = NodeOperator)]
#[pyclass(module = "ast", name = "Div", base = NodeOperator)]
pub(crate) struct NodeOperatorDiv,
);
impl_node!(
#[pyclass(module = "_ast", name = "Mod", base = NodeOperator)]
#[pyclass(module = "ast", name = "Mod", base = NodeOperator)]
pub(crate) struct NodeOperatorMod,
);
impl_node!(
#[pyclass(module = "_ast", name = "Pow", base = NodeOperator)]
#[pyclass(module = "ast", name = "Pow", base = NodeOperator)]
pub(crate) struct NodeOperatorPow,
);
impl_node!(
#[pyclass(module = "_ast", name = "LShift", base = NodeOperator)]
#[pyclass(module = "ast", name = "LShift", base = NodeOperator)]
pub(crate) struct NodeOperatorLShift,
);
impl_node!(
#[pyclass(module = "_ast", name = "RShift", base = NodeOperator)]
#[pyclass(module = "ast", name = "RShift", base = NodeOperator)]
pub(crate) struct NodeOperatorRShift,
);
impl_node!(
#[pyclass(module = "_ast", name = "BitOr", base = NodeOperator)]
#[pyclass(module = "ast", name = "BitOr", base = NodeOperator)]
pub(crate) struct NodeOperatorBitOr,
);
impl_node!(
#[pyclass(module = "_ast", name = "BitXor", base = NodeOperator)]
#[pyclass(module = "ast", name = "BitXor", base = NodeOperator)]
pub(crate) struct NodeOperatorBitXor,
);
impl_node!(
#[pyclass(module = "_ast", name = "BitAnd", base = NodeOperator)]
#[pyclass(module = "ast", name = "BitAnd", base = NodeOperator)]
pub(crate) struct NodeOperatorBitAnd,
);
#[pyclass(module = "_ast", name = "unaryop", base = NodeAst)]
#[pyclass(module = "ast", name = "unaryop", base = NodeAst)]
#[repr(transparent)]
pub(crate) struct NodeUnaryOp(NodeAst);
impl_base_node!(NodeUnaryOp);
impl_node!(
#[pyclass(module = "_ast", name = "FloorDiv", base = NodeOperator)]
#[pyclass(module = "ast", name = "FloorDiv", base = NodeOperator)]
pub(crate) struct NodeOperatorFloorDiv,
);
impl_node!(
#[pyclass(module = "_ast", name = "Invert", base = NodeUnaryOp)]
#[pyclass(module = "ast", name = "Invert", base = NodeUnaryOp)]
pub(crate) struct NodeUnaryOpInvert,
);
impl_node!(
#[pyclass(module = "_ast", name = "Not", base = NodeUnaryOp)]
#[pyclass(module = "ast", name = "Not", base = NodeUnaryOp)]
pub(crate) struct NodeUnaryOpNot,
);
impl_node!(
#[pyclass(module = "_ast", name = "UAdd", base = NodeUnaryOp)]
#[pyclass(module = "ast", name = "UAdd", base = NodeUnaryOp)]
pub(crate) struct NodeUnaryOpUAdd,
);
#[pyclass(module = "_ast", name = "cmpop", base = NodeAst)]
#[pyclass(module = "ast", name = "cmpop", base = NodeAst)]
#[repr(transparent)]
pub(crate) struct NodeCmpOp(NodeAst);
impl_base_node!(NodeCmpOp);
impl_node!(
#[pyclass(module = "_ast", name = "USub", base = NodeUnaryOp)]
#[pyclass(module = "ast", name = "USub", base = NodeUnaryOp)]
pub(crate) struct NodeUnaryOpUSub,
);
impl_node!(
#[pyclass(module = "_ast", name = "Eq", base = NodeCmpOp)]
#[pyclass(module = "ast", name = "Eq", base = NodeCmpOp)]
pub(crate) struct NodeCmpOpEq,
);
impl_node!(
#[pyclass(module = "_ast", name = "NotEq", base = NodeCmpOp)]
#[pyclass(module = "ast", name = "NotEq", base = NodeCmpOp)]
pub(crate) struct NodeCmpOpNotEq,
);
impl_node!(
#[pyclass(module = "_ast", name = "Lt", base = NodeCmpOp)]
#[pyclass(module = "ast", name = "Lt", base = NodeCmpOp)]
pub(crate) struct NodeCmpOpLt,
);
impl_node!(
#[pyclass(module = "_ast", name = "LtE", base = NodeCmpOp)]
#[pyclass(module = "ast", name = "LtE", base = NodeCmpOp)]
pub(crate) struct NodeCmpOpLtE,
);
impl_node!(
#[pyclass(module = "_ast", name = "Gt", base = NodeCmpOp)]
#[pyclass(module = "ast", name = "Gt", base = NodeCmpOp)]
pub(crate) struct NodeCmpOpGt,
);
impl_node!(
#[pyclass(module = "_ast", name = "GtE", base = NodeCmpOp)]
#[pyclass(module = "ast", name = "GtE", base = NodeCmpOp)]
pub(crate) struct NodeCmpOpGtE,
);
impl_node!(
#[pyclass(module = "_ast", name = "Is", base = NodeCmpOp)]
#[pyclass(module = "ast", name = "Is", base = NodeCmpOp)]
pub(crate) struct NodeCmpOpIs,
);
impl_node!(
#[pyclass(module = "_ast", name = "IsNot", base = NodeCmpOp)]
#[pyclass(module = "ast", name = "IsNot", base = NodeCmpOp)]
pub(crate) struct NodeCmpOpIsNot,
);
impl_node!(
#[pyclass(module = "_ast", name = "In", base = NodeCmpOp)]
#[pyclass(module = "ast", name = "In", base = NodeCmpOp)]
pub(crate) struct NodeCmpOpIn,
);
impl_node!(
#[pyclass(module = "_ast", name = "NotIn", base = NodeCmpOp)]
#[pyclass(module = "ast", name = "NotIn", base = NodeCmpOp)]
pub(crate) struct NodeCmpOpNotIn,
);
#[pyclass(module = "_ast", name = "excepthandler", base = NodeAst)]
#[pyclass(module = "ast", name = "excepthandler", base = NodeAst)]
#[repr(transparent)]
pub(crate) struct NodeExceptHandler(NodeAst);
@@ -864,52 +852,52 @@ impl_base_node!(
);
impl_node!(
#[pyclass(module = "_ast", name = "comprehension", base = NodeAst)]
#[pyclass(module = "ast", name = "comprehension", base = NodeAst)]
pub(crate) struct NodeComprehension,
fields: ["target", "iter", "ifs", "is_async"],
);
impl_node!(
#[pyclass(module = "_ast", name = "ExceptHandler", base = NodeExceptHandler)]
#[pyclass(module = "ast", name = "ExceptHandler", base = NodeExceptHandler)]
pub(crate) struct NodeExceptHandlerExceptHandler,
fields: ["type", "name", "body"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "arguments", base = NodeAst)]
#[pyclass(module = "ast", name = "arguments", base = NodeAst)]
pub(crate) struct NodeArguments,
fields: ["posonlyargs", "args", "vararg", "kwonlyargs", "kw_defaults", "kwarg", "defaults"],
);
impl_node!(
#[pyclass(module = "_ast", name = "arg", base = NodeAst)]
#[pyclass(module = "ast", name = "arg", base = NodeAst)]
pub(crate) struct NodeArg,
fields: ["arg", "annotation", "type_comment"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "keyword", base = NodeAst)]
#[pyclass(module = "ast", name = "keyword", base = NodeAst)]
pub(crate) struct NodeKeyword,
fields: ["arg", "value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "alias", base = NodeAst)]
#[pyclass(module = "ast", name = "alias", base = NodeAst)]
pub(crate) struct NodeAlias,
fields: ["name", "asname"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "withitem", base = NodeAst)]
#[pyclass(module = "ast", name = "withitem", base = NodeAst)]
pub(crate) struct NodeWithItem,
fields: ["context_expr", "optional_vars"],
);
#[pyclass(module = "_ast", name = "pattern", base = NodeAst)]
#[pyclass(module = "ast", name = "pattern", base = NodeAst)]
#[repr(transparent)]
pub(crate) struct NodePattern(NodeAst);
@@ -919,74 +907,74 @@ impl_base_node!(
);
impl_node!(
#[pyclass(module = "_ast", name = "match_case", base = NodeAst)]
#[pyclass(module = "ast", name = "match_case", base = NodeAst)]
pub(crate) struct NodeMatchCase,
fields: ["pattern", "guard", "body"],
);
impl_node!(
#[pyclass(module = "_ast", name = "MatchValue", base = NodePattern)]
#[pyclass(module = "ast", name = "MatchValue", base = NodePattern)]
pub(crate) struct NodePatternMatchValue,
fields: ["value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "MatchSingleton", base = NodePattern)]
#[pyclass(module = "ast", name = "MatchSingleton", base = NodePattern)]
pub(crate) struct NodePatternMatchSingleton,
fields: ["value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "MatchSequence", base = NodePattern)]
#[pyclass(module = "ast", name = "MatchSequence", base = NodePattern)]
pub(crate) struct NodePatternMatchSequence,
fields: ["patterns"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "MatchMapping", base = NodePattern)]
#[pyclass(module = "ast", name = "MatchMapping", base = NodePattern)]
pub(crate) struct NodePatternMatchMapping,
fields: ["keys", "patterns", "rest"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "MatchClass", base = NodePattern)]
#[pyclass(module = "ast", name = "MatchClass", base = NodePattern)]
pub(crate) struct NodePatternMatchClass,
fields: ["cls", "patterns", "kwd_attrs", "kwd_patterns"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "MatchStar", base = NodePattern)]
#[pyclass(module = "ast", name = "MatchStar", base = NodePattern)]
pub(crate) struct NodePatternMatchStar,
fields: ["name"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "MatchAs", base = NodePattern)]
#[pyclass(module = "ast", name = "MatchAs", base = NodePattern)]
pub(crate) struct NodePatternMatchAs,
fields: ["pattern", "name"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
#[pyclass(module = "_ast", name = "type_ignore", base = NodeAst)]
#[pyclass(module = "ast", name = "type_ignore", base = NodeAst)]
#[repr(transparent)]
pub(crate) struct NodeTypeIgnore(NodeAst);
impl_base_node!(NodeTypeIgnore);
impl_node!(
#[pyclass(module = "_ast", name = "MatchOr", base = NodePattern)]
#[pyclass(module = "ast", name = "MatchOr", base = NodePattern)]
pub(crate) struct NodePatternMatchOr,
fields: ["patterns"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
#[pyclass(module = "_ast", name = "type_param", base = NodeAst)]
#[pyclass(module = "ast", name = "type_param", base = NodeAst)]
#[repr(transparent)]
pub(crate) struct NodeTypeParam(NodeAst);
@@ -996,27 +984,27 @@ impl_base_node!(
);
impl_node!(
#[pyclass(module = "_ast", name = "TypeIgnore", base = NodeTypeIgnore)]
#[pyclass(module = "ast", name = "TypeIgnore", base = NodeTypeIgnore)]
pub(crate) struct NodeTypeIgnoreTypeIgnore,
fields: ["lineno", "tag"],
);
impl_node!(
#[pyclass(module = "_ast", name = "TypeVar", base = NodeTypeParam)]
#[pyclass(module = "ast", name = "TypeVar", base = NodeTypeParam)]
pub(crate) struct NodeTypeParamTypeVar,
fields: ["name", "bound", "default_value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "ParamSpec", base = NodeTypeParam)]
#[pyclass(module = "ast", name = "ParamSpec", base = NodeTypeParam)]
pub(crate) struct NodeTypeParamParamSpec,
fields: ["name", "default_value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
);
impl_node!(
#[pyclass(module = "_ast", name = "TypeVarTuple", base = NodeTypeParam)]
#[pyclass(module = "ast", name = "TypeVarTuple", base = NodeTypeParam)]
pub(crate) struct NodeTypeParamTypeVarTuple,
fields: ["name", "default_value"],
attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"],
+6 -8
View File
@@ -16,7 +16,7 @@ pub(crate) mod _ast {
warn,
};
#[pyattr]
#[pyclass(module = "_ast", name = "AST")]
#[pyclass(module = "ast", name = "AST")]
#[derive(Debug, PyPayload)]
pub(crate) struct NodeAst;
@@ -24,13 +24,11 @@ pub(crate) mod _ast {
impl NodeAst {
#[extend_class]
fn extend_class(ctx: &Context, class: &'static Py<PyType>) {
// AST types are mutable (heap types, not IMMUTABLETYPE)
// Safety: called during type initialization before any concurrent access
unsafe {
let flags = &class.slots.flags as *const crate::types::PyTypeFlags
as *mut crate::types::PyTypeFlags;
(*flags).remove(crate::types::PyTypeFlags::IMMUTABLETYPE);
}
// AST types are mutable (heap types, not IMMUTABLETYPE).
class
.slots
.flags
.remove(crate::types::PyTypeFlags::IMMUTABLETYPE);
let empty_tuple = ctx.empty_tuple.clone();
class.set_str_attr("_fields", empty_tuple.clone(), ctx);
class.set_str_attr("_attributes", empty_tuple.clone(), ctx);
+3 -3
View File
@@ -312,9 +312,9 @@ pub(crate) mod _ctypes {
// Create slots with IMMUTABLETYPE flag
let slots = PyTypeSlots {
name: "COMError",
flags: PyTypeFlags::heap_type_flags()
| PyTypeFlags::HAS_DICT
| PyTypeFlags::IMMUTABLETYPE,
flags: crate::types::PyAtomicTypeFlags::new(
PyTypeFlags::heap_type_flags() | PyTypeFlags::HAS_DICT | PyTypeFlags::IMMUTABLETYPE,
),
..PyTypeSlots::default()
};
+11 -1
View File
@@ -2004,7 +2004,17 @@ impl PyCThunk {
unsafe impl Send for PyCThunk {}
unsafe impl Sync for PyCThunk {}
#[pyclass]
/// `ffi_type`: `size_t` size, two `unsigned short`s, then a pointer.
#[repr(C)]
#[allow(dead_code)]
struct FfiTypeLayout {
_size: usize,
_alignment: u16,
_type_code: u16,
_elements: *mut *mut u8,
}
#[pyclass(itemsize = core::mem::size_of::<FfiTypeLayout>())]
impl PyCThunk {
#[pygetset]
fn callable(&self) -> PyObjectRef {
+42 -84
View File
@@ -3,7 +3,7 @@ pub(crate) use _functools::module_def;
#[pymodule]
mod _functools {
use crate::{
Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{
PyBoundMethod, PyDict, PyDictRef, PyGenericAlias, PyTuple, PyType, PyTypeRef, object,
},
@@ -11,13 +11,13 @@ mod _functools {
function::{
Either, FuncArgs, KwArgs, OptionalOption, PosArgs, PyComparisonValue, PySetterValue,
},
object::{AsObject, TraverseFn},
object::{AsObject, PyAtomicRef, TraverseFn},
protocol::PyIter,
pyclass,
recursion::ReprGuard,
types::{Callable, Constructor, GetDescriptor, PyComparisonOp, Representable},
};
use core::sync::atomic::{AtomicU64, Ordering};
use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use parking_lot::lock_api::RawReentrantMutex as GenericRawReentrantMutex;
use rustpython_common::wtf8::Wtf8Buf;
@@ -225,34 +225,27 @@ mod _functools {
#[pyclass(name = "partial", module = "functools", traverse = "manual")]
#[derive(Debug, PyPayload)]
pub(super) struct PyPartial {
inner: PyRwLock<PyPartialInner>,
}
#[derive(Debug)]
struct PyPartialInner {
func: PyObjectRef,
args: PyRef<PyTuple>,
keywords: PyRef<PyDict>,
phcount: usize,
#[pymember]
func: PyAtomicRef<PyObject>,
#[pymember]
args: PyAtomicRef<PyTuple>,
#[pymember]
keywords: PyAtomicRef<PyDict>,
phcount: AtomicUsize,
}
// SAFETY: Each owned reference is visited once, without cloning it.
unsafe impl crate::object::Traverse for PyPartial {
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
if let Some(inner) = self.inner.try_read_recursive() {
inner.func.traverse(tracer_fn);
inner.args.traverse(tracer_fn);
inner.keywords.traverse(tracer_fn);
}
self.func.traverse(tracer_fn);
self.args.traverse(tracer_fn);
self.keywords.traverse(tracer_fn);
}
fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
// __setstate__ can make func point directly back to this partial.
// The args tuple and keywords dict have their own cycle clearing.
out.push(core::mem::replace(
&mut self.inner.get_mut().func,
Context::genesis().none(),
));
out.push(self.func.store(Context::genesis().none()));
}
}
@@ -261,21 +254,6 @@ mod _functools {
flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
)]
impl PyPartial {
#[pygetset]
fn func(&self) -> PyObjectRef {
self.inner.read().func.clone()
}
#[pygetset]
fn args(&self) -> PyRef<PyTuple> {
self.inner.read().args.clone()
}
#[pygetset]
fn keywords(&self) -> PyRef<PyDict> {
self.inner.read().keywords.clone()
}
#[pygetset]
fn __dict__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyDictRef {
zelf.as_object()
@@ -294,7 +272,9 @@ mod _functools {
#[pymethod]
fn __reduce__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyObjectRef {
let inner = zelf.inner.read();
let func = zelf.func.load_owned();
let args = zelf.args.load_owned();
let keywords = zelf.keywords.load_owned();
let partial_type = zelf.class();
// Get __dict__ if it exists and is not empty
@@ -303,16 +283,13 @@ mod _functools {
_ => vm.ctx.none(),
};
let state = vm.ctx.new_tuple(vec![
inner.func.clone(),
inner.args.clone().into(),
inner.keywords.clone().into(),
dict_obj,
]);
let state =
vm.ctx
.new_tuple(vec![func.clone(), args.into(), keywords.into(), dict_obj]);
vm.ctx
.new_tuple(vec![
partial_type.to_owned().into(),
vm.ctx.new_tuple(vec![inner.func.clone()]).into(),
vm.ctx.new_tuple(vec![func]).into(),
state.into(),
])
.into()
@@ -383,19 +360,10 @@ mod _functools {
}
let phcount = count_placeholders(args_slice);
// Actually update the state
let old_inner = core::mem::replace(
&mut *zelf.inner.write(),
PyPartialInner {
func: func.clone(),
// Handle args - use the already validated tuple
args: args_tuple,
// Handle keywords - keep the original type
keywords: keywords_dict,
phcount,
},
);
drop(old_inner);
zelf.func.store(func.clone());
zelf.args.store(args_tuple);
zelf.keywords.store(keywords_dict);
zelf.phcount.store(phcount, Ordering::Relaxed);
// Update __dict__ if provided
let Some(instance_dict) = zelf.as_object().dict() else {
@@ -489,8 +457,10 @@ mod _functools {
// Handle nested partial objects
let (final_func, final_args, final_keywords) =
if let Some(partial) = func.downcast_ref::<Self>() {
let inner = partial.inner.read();
let stored_args = inner.args.as_slice();
let stored_args_obj = partial.args.load_owned();
let stored_func = partial.func.load_owned();
let stored_keywords = partial.keywords.load_owned();
let stored_args = stored_args_obj.as_slice();
// Merge placeholders: replace placeholders in stored_args with new args
let mut merged_args = Vec::with_capacity(stored_args.len() + args_slice.len());
@@ -511,7 +481,7 @@ mod _functools {
// Append remaining new args
merged_args.extend(new_args_iter.cloned());
(inner.func.clone(), merged_args, inner.keywords.clone())
(stored_func, merged_args, stored_keywords)
} else {
(func.clone(), args_slice.to_vec(), vm.ctx.new_dict())
};
@@ -529,12 +499,10 @@ mod _functools {
}
Ok(Self {
inner: PyRwLock::new(PyPartialInner {
func: final_func,
args: vm.ctx.new_tuple(final_args),
keywords: final_keywords,
phcount,
}),
func: PyAtomicRef::from(final_func),
args: PyAtomicRef::from(vm.ctx.new_tuple(final_args)),
keywords: PyAtomicRef::from(final_keywords),
phcount: AtomicUsize::new(phcount),
})
}
}
@@ -544,15 +512,10 @@ mod _functools {
fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
// Clone and release lock before calling Python code to prevent deadlock
let (func, stored_args, keywords, phcount) = {
let inner = zelf.inner.read();
(
inner.func.clone(),
inner.args.clone(),
inner.keywords.clone(),
inner.phcount,
)
};
let func = zelf.func.load_owned();
let stored_args = zelf.args.load_owned();
let keywords = zelf.keywords.load_owned();
let phcount = zelf.phcount.load(Ordering::Relaxed);
// Check if we have enough args to fill placeholders
if phcount > 0 && args.args.len() < phcount {
@@ -629,14 +592,9 @@ mod _functools {
let obj = zelf.as_object();
if let Some(_guard) = ReprGuard::enter(vm, obj) {
// Clone and release lock before calling Python code to prevent deadlock
let (func, args, keywords) = {
let inner = zelf.inner.read();
(
inner.func.clone(),
inner.args.clone(),
inner.keywords.clone(),
)
};
let func = zelf.func.load_owned();
let args = zelf.args.load_owned();
let keywords = zelf.keywords.load_owned();
let qualname = zelf.class().__qualname__(vm);
let qualname_wtf8 = qualname
@@ -645,7 +603,7 @@ mod _functools {
|| Wtf8Buf::from(zelf.class().name().to_owned()),
|s| s.as_wtf8().to_owned(),
);
let module = zelf.class().__module__(vm);
let module = zelf.class().__module__(vm)?;
let mut result = Wtf8Buf::new();
if let Ok(module_str) = module.downcast::<crate::builtins::PyStr>() {
File diff suppressed because it is too large. Load diff
+19 -3
View File
@@ -263,7 +263,15 @@ mod _sre {
items: Vec<(usize, PyObjectRef)>,
}
#[pyclass]
/// One `TemplateObject.items` element: `Py_ssize_t` index, object pointer.
#[repr(C)]
#[allow(dead_code)]
struct SreTemplateItem {
_index: isize,
_literal: *mut PyObject,
}
#[pyclass(itemsize = core::mem::size_of::<SreTemplateItem>())]
impl Template {
fn compile(
pattern: PyRef<Pattern>,
@@ -378,7 +386,11 @@ mod _sre {
}};
}
#[pyclass(with(Hashable, Comparable, Representable), flags(HAS_WEAKREF))]
#[pyclass(
itemsize = core::mem::size_of::<u32>(),
with(Hashable, Comparable, Representable),
flags(HAS_WEAKREF)
)]
impl Pattern {
fn downcast_str<'a>(string: &'a PyObject, vm: &VirtualMachine) -> PyResult<&'a Py<PyStr>> {
string.downcast_ref::<PyStr>().ok_or_else(|| {
@@ -817,7 +829,11 @@ mod _sre {
regs: Vec<(isize, isize)>,
}
#[pyclass(with(AsMapping, Representable), flags(DISALLOW_INSTANTIATION))]
#[pyclass(
itemsize = core::mem::size_of::<isize>(),
with(AsMapping, Representable),
flags(DISALLOW_INSTANTIATION)
)]
impl Match {
pub(crate) fn new(state: &mut State, pattern: PyRef<Pattern>, string: PyObjectRef) -> Self {
let string_position = state.cursor.position;
+50 -62
View File
@@ -31,7 +31,7 @@ pub(crate) mod _thread {
OptionalArg, Param, ParamKind, PosArgs, PySetterValue, TimeoutSeconds,
},
object::{Traverse, TraverseFn},
types::{Constructor, GetAttr, Representable, SetAttr},
types::{Constructor, GetAttr, PyStructSequence, Representable, SetAttr},
};
use alloc::sync::{Arc, Weak};
@@ -1000,81 +1000,69 @@ pub(crate) mod _thread {
}
// This allows threading.py to import _excepthook and _ExceptHookArgs from _thread
#[pyattr]
#[pyclass(module = "_thread", name = "_ExceptHookArgs")]
#[derive(Debug, PyPayload)]
struct ExceptHookArgs {
#[pystruct_sequence_data]
struct ExceptHookArgsData {
exc_type: crate::PyObjectRef,
exc_value: crate::PyObjectRef,
exc_traceback: crate::PyObjectRef,
thread: crate::PyObjectRef,
}
#[pyclass(with(Constructor))]
impl ExceptHookArgs {
#[pygetset]
fn exc_type(&self) -> crate::PyObjectRef {
self.exc_type.clone()
}
#[pyattr]
#[pystruct_sequence(
name = "_ExceptHookArgs",
module = "_thread",
data = "ExceptHookArgsData"
)]
struct PyExceptHookArgs;
#[pygetset]
fn exc_value(&self) -> crate::PyObjectRef {
self.exc_value.clone()
}
#[pygetset]
fn exc_traceback(&self) -> crate::PyObjectRef {
self.exc_traceback.clone()
}
#[pygetset]
fn thread(&self) -> crate::PyObjectRef {
self.thread.clone()
}
}
#[derive(FromArgs)]
struct ExceptHookNewArgs {
#[pyarg(positional, default, py_default = "()")]
iterable: crate::function::OptionalArg<crate::PyObjectRef>,
}
impl Constructor for ExceptHookArgs {
// Takes a single iterable argument like namedtuple
type Args = ExceptHookNewArgs;
fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
// Convert the argument to a list/tuple and extract elements
let seq: Vec<crate::PyObjectRef> = match args.iterable {
crate::function::OptionalArg::Present(iterable) => iterable.try_to_value(vm)?,
crate::function::OptionalArg::Missing => Vec::new(),
};
if seq.len() != 4 {
return Err(vm.new_type_error(format!(
"_ExceptHookArgs expected 4 arguments, got {}",
seq.len()
)));
}
Ok(Self {
exc_type: seq[0].clone(),
exc_value: seq[1].clone(),
exc_traceback: seq[2].clone(),
thread: seq[3].clone(),
})
#[pyclass(with(PyStructSequence))]
impl PyExceptHookArgs {
#[extend_class]
fn extend_pyclass(ctx: &crate::vm::Context, class: &'static Py<crate::builtins::PyType>) {
// (type, (sequence, dict)). Set before the trait installs its default.
const EXCEPT_HOOK_ARGS_REDUCE: crate::function::PyMethodDef =
crate::function::PyMethodDef::new_const(
"__reduce__",
|zelf: crate::PyRef<crate::builtins::PyTuple>,
vm: &VirtualMachine|
-> PyTupleRef {
vm.new_tuple((
zelf.class().to_owned(),
(
vm.ctx.new_tuple(zelf.as_slice().to_vec()),
vm.ctx.new_dict(),
),
))
},
crate::function::PyMethodFlags::METHOD,
crate::function::ItemDoc::NONE,
);
class.set_attr(
ctx.intern_str("__reduce__"),
EXCEPT_HOOK_ARGS_REDUCE.to_proper_method(class, ctx),
);
}
}
#[pyfunction]
fn _excepthook(args: crate::PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
// Type check: args must be _ExceptHookArgs
let args = args.downcast::<ExceptHookArgs>().map_err(|_| {
vm.new_type_error("_thread._excepthook argument type must be _ExceptHookArgs")
let args = args.downcast::<PyExceptHookArgs>().map_err(|_| {
vm.new_type_error("_thread.excepthook argument type must be ExceptHookArgs")
})?;
let exc_type = args.exc_type.clone();
let exc_value = args.exc_value.clone();
let exc_traceback = args.exc_traceback.clone();
let thread = args.thread.clone();
let fields = args.0.as_slice();
let (Some(exc_type), Some(exc_value), Some(exc_traceback), Some(thread)) =
(fields.first(), fields.get(1), fields.get(2), fields.get(3))
else {
return Err(
vm.new_type_error("_thread.excepthook argument type must be ExceptHookArgs")
);
};
let exc_type = exc_type.clone();
let exc_value = exc_value.clone();
let exc_traceback = exc_traceback.clone();
let thread = thread.clone();
// Silently ignore SystemExit (identity check)
if exc_type.is(vm.ctx.exceptions.system_exit.as_ref()) {
+1 -1
View File
@@ -1187,7 +1187,7 @@ pub mod sys {
);
// Print module name (if not builtins or __main__)
let module_name = unraisable.exc_type.__module__(vm);
let module_name = unraisable.exc_type.__module__(vm)?;
if let Ok(module_str) = module_name.downcast::<PyStr>() {
let module = module_str.as_wtf8();
if module != "builtins" && module != "__main__" {
+107 -3
View File
@@ -134,6 +134,8 @@ pub struct PyTypeSlots {
/// For heap types, `__name__` must alive
pub(crate) name: &'static str, // tp_name with <module>.<class> for print, not class name
/// Full `tp_basicsize`: object header plus payload. `0` before type
/// creation means "inherit the base size".
pub basicsize: usize,
pub itemsize: usize, // tp_itemsize
@@ -171,8 +173,9 @@ pub struct PyTypeSlots {
pub methods: &'static [PyMethodDef],
// Flags to define presence of optional/expanded features
pub flags: PyTypeFlags,
// Flags to define presence of optional/expanded features.
// One atomic word: runtime code sets and clears bits in place.
pub flags: PyAtomicTypeFlags,
// tp_doc
pub doc: ItemDoc,
@@ -215,7 +218,7 @@ impl PyTypeSlots {
pub fn new(name: &'static str, flags: PyTypeFlags) -> Self {
Self {
name,
flags,
flags: PyAtomicTypeFlags::new(flags),
..Default::default()
}
}
@@ -300,6 +303,107 @@ impl Default for PyTypeFlags {
}
}
/// `tp_flags` as one atomic word. Reads match [`PyTypeFlags`]; bits that change
/// after the type is published are set and cleared in place.
#[repr(transparent)]
pub struct PyAtomicTypeFlags(core::sync::atomic::AtomicU64);
impl core::fmt::Debug for PyAtomicTypeFlags {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.load().fmt(f)
}
}
impl Default for PyAtomicTypeFlags {
fn default() -> Self {
Self::new(PyTypeFlags::DEFAULT)
}
}
impl PyAtomicTypeFlags {
#[must_use]
pub const fn new(flags: PyTypeFlags) -> Self {
Self(core::sync::atomic::AtomicU64::new(flags.bits()))
}
#[must_use]
pub fn load(&self) -> PyTypeFlags {
PyTypeFlags::from_bits_truncate(self.bits())
}
#[must_use]
pub fn bits(&self) -> u64 {
self.0.load(core::sync::atomic::Ordering::Acquire)
}
#[must_use]
pub fn has_feature(&self, flag: PyTypeFlags) -> bool {
self.load().has_feature(flag)
}
#[must_use]
pub fn contains(&self, flag: PyTypeFlags) -> bool {
self.load().contains(flag)
}
#[must_use]
pub fn intersects(&self, flag: PyTypeFlags) -> bool {
self.load().intersects(flag)
}
pub fn remove(&self, flag: PyTypeFlags) {
self.0
.fetch_and(!flag.bits(), core::sync::atomic::Ordering::AcqRel);
}
pub fn set(&self, flag: PyTypeFlags) {
self.0
.fetch_or(flag.bits(), core::sync::atomic::Ordering::AcqRel);
}
/// Replace `mask` bits with `value & mask`.
pub fn replace_masked(&self, mask: PyTypeFlags, value: PyTypeFlags) {
let mask_bits = mask.bits();
let value_bits = (value & mask).bits();
let _ = self.0.fetch_update(
core::sync::atomic::Ordering::AcqRel,
core::sync::atomic::Ordering::Acquire,
|old| Some((old & !mask_bits) | value_bits),
);
}
#[cfg(debug_assertions)]
#[must_use]
pub fn is_created_with_flags(&self) -> bool {
self.load().is_created_with_flags()
}
}
impl core::ops::BitOrAssign<PyTypeFlags> for PyAtomicTypeFlags {
fn bitor_assign(&mut self, rhs: PyTypeFlags) {
self.set(rhs);
}
}
const _: () = assert!(
core::mem::size_of::<PyAtomicTypeFlags>()
== core::mem::size_of::<core::sync::atomic::AtomicU64>()
&& core::mem::align_of::<PyAtomicTypeFlags>()
== core::mem::align_of::<core::sync::atomic::AtomicU64>()
&& core::mem::size_of::<core::sync::atomic::AtomicU64>() == core::mem::size_of::<u64>()
&& core::mem::align_of::<core::sync::atomic::AtomicU64>() == core::mem::align_of::<u64>()
);
impl crate::builtins::descriptor::MemberLayout for PyAtomicTypeFlags {
const KIND: crate::builtins::descriptor::MemberKind = {
if core::mem::size_of::<core::ffi::c_ulong>() == 8 {
crate::builtins::descriptor::MemberKind::ULong
} else {
crate::builtins::descriptor::MemberKind::ULongLong
}
};
}
pub(crate) type GenericMethod = fn(&PyObject, FuncArgs, &VirtualMachine) -> PyResult;
/// Vectorcall function pointer (PEP 590).
/// args: owned positional args followed by kwarg values.
+3 -1
View File
@@ -622,7 +622,9 @@ impl Context {
let slots = PyTypeSlots {
name: interned_name.as_str(),
basicsize: 0,
flags: PyTypeFlags::heap_type_flags() | PyTypeFlags::HAS_DICT,
flags: crate::types::PyAtomicTypeFlags::new(
PyTypeFlags::heap_type_flags() | PyTypeFlags::HAS_DICT,
),
..PyTypeSlots::default()
};
PyType::new_heap(
+9 -4
View File
@@ -434,7 +434,7 @@ impl VirtualMachine {
) -> PyBaseExceptionRef {
debug_assert_eq!(
exc_type.slots.basicsize,
core::mem::size_of::<PyBaseException>(),
crate::object::SIZEOF_PYOBJECT_HEAD + core::mem::size_of::<PyBaseException>(),
"vm.new_simple_exception() requires a BaseException-sized type, got {}",
exc_type.name()
);
@@ -452,7 +452,9 @@ impl VirtualMachine {
/// [`invoke_exception`][Self::invoke_exception] or
/// [`exceptions::ExceptionCtor`][crate::exceptions::ExceptionCtor].
pub fn new_exception(&self, exc_type: PyTypeRef, args: Vec<PyObjectRef>) -> PyBaseExceptionRef {
if exc_type.slots.basicsize == core::mem::size_of::<PyBaseException>() {
if exc_type.slots.basicsize
== crate::object::SIZEOF_PYOBJECT_HEAD + core::mem::size_of::<PyBaseException>()
{
self.new_simple_exception(exc_type, args)
} else {
self.invoke_exception(&exc_type, args).unwrap_or_else(|e| e)
@@ -468,7 +470,7 @@ impl VirtualMachine {
{
debug_assert_eq!(
cls.slots.basicsize,
size_of::<T>(),
crate::object::SIZEOF_PYOBJECT_HEAD + size_of::<T>(),
"vm.new_payload_exception::<{}>() called with mismatched type '{}'",
core::any::type_name::<T>(),
cls.name()
@@ -494,7 +496,10 @@ impl VirtualMachine {
errno: Option<i32>,
msg: impl ToPyObject,
) -> PyRef<PyOSError> {
debug_assert_eq!(exc_type.slots.basicsize, core::mem::size_of::<PyOSError>());
debug_assert_eq!(
exc_type.slots.basicsize,
crate::object::SIZEOF_PYOBJECT_HEAD + core::mem::size_of::<PyOSError>()
);
OSErrorBuilder::with_subtype(exc_type, errno, msg, self).build(self)
}