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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions Lib/test/test_pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,6 @@ def loads(self, buf, **kwds):
u = self.unpickler(f, **kwds)
return u.load()

Comment thread
1ndahous3 marked this conversation as resolved.
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_c_methods(self):
return super().test_c_methods()

@unittest.expectedFailureIf(sys.getrecursionlimit() == 256, "TODO: RUSTPYTHON; default debug recursion limit is 256")
def test_deep_nested_struct_frozendict(self):
return super().test_deep_nested_struct_frozendict()
Expand All @@ -126,10 +122,6 @@ def loads(self, buf, **kwds):
test_find_class = None
test_custom_find_class = None

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_c_methods(self):
return super().test_c_methods()


class PersistentPicklerUnpicklerMixin(object):

Expand Down
4 changes: 0 additions & 4 deletions Lib/test/test_pickletools.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ def test_optimize_binput_and_memoize(self):
self.assertIs(unpickled2[1], unpickled2[2])
self.assertNotIn(pickle.BINPUT, pickled2)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_c_methods(self):
return super().test_c_methods()


class SimpleReader:
def __init__(self, data):
Expand Down
69 changes: 68 additions & 1 deletion crates/vm/src/builtins/staticmethod.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
use super::{
PyGenericAlias, PyStr, PyType, PyTypeRef,
PyDictRef, PyGenericAlias, PyStr, PyType, PyTypeRef,
builtin_func::PyNativeFunction,
classmethod::{
descriptor_get_wrapped_attribute, descriptor_set_wrapped_attribute, functools_wraps,
},
type_::{PyAttributes, rendered_item_doc},
};
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
class::{PyClassDef, PyClassImpl},
common::{hash::HashSecret, lock::PyMutex, wtf8::Wtf8Buf},
function::{FuncArgs, PySetterValue},
object::PyAtomicRef,
types::{Callable, Constructor, GetDescriptor, Initializer, Representable},
};

// Context::genesis builds native descriptors before the first interpreter
// chooses its hash seed. Delay hashing their metadata until that seed exists.
rustpython_common::static_cell! {
static NATIVE_METADATA: PyMutex<NativeMetadata>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's check if this is really necessary or not

}

#[derive(Default)]
struct NativeMetadata {
hash_secret: Option<&'static HashSecret>,
pending: Vec<(PyDictRef, PyAttributes)>,
}

#[pyclass(module = false, name = "staticmethod", traverse)]
#[derive(Debug)]
pub struct PyStaticMethod {
Expand Down Expand Up @@ -75,6 +90,58 @@ impl Constructor for PyStaticMethod {
}

impl PyStaticMethod {
pub(crate) fn new_native(
callable: PyRef<PyNativeFunction>,
class: &Py<PyType>,
ctx: &Context,
) -> PyRef<Self> {
let mut qualname = class.heaptype_ext.as_ref().map_or_else(
|| Wtf8Buf::from_string(class.name().to_string()),
|ext| ext.qualname.read().as_wtf8().to_owned(),
);
qualname.push_str(".");
qualname.push_str(callable.value.name);
let attrs = [
Comment thread
1ndahous3 marked this conversation as resolved.
(
identifier!(ctx, __module__),
callable.module.load_owned().unwrap_or_else(|| ctx.none()),
),
(
identifier!(ctx, __name__),
ctx.new_str(callable.value.name).into(),
),
(identifier!(ctx, __qualname__), ctx.new_str(qualname).into()),
(
identifier!(ctx, __doc__),
rendered_item_doc(callable.value.name, callable.value.item_doc())
.map_or_else(|| ctx.none(), |doc| ctx.new_str(doc).into()),
),
]
.into_iter()
.collect();
let dict = ctx.new_dict();
let wrapper = PyRef::new_ref(
Self::new(callable.into()),
ctx.types.staticmethod_type.to_owned(),
Some(dict.clone()),
);
let mut metadata = NATIVE_METADATA.get_or_init(PyMutex::default).lock();
if let Some(secret) = metadata.hash_secret {
dict.entries.initialize_attributes(attrs, secret);
} else {
metadata.pending.push((dict, attrs));
}
wrapper
}

pub(crate) fn init_native_metadata(secret: &'static HashSecret) {
let mut metadata = NATIVE_METADATA.get_or_init(PyMutex::default).lock();
metadata.hash_secret = Some(secret);
for (dict, attrs) in metadata.pending.drain(..) {
dict.entries.initialize_attributes(attrs, secret);
}
}

#[must_use]
pub fn new(callable: PyObjectRef) -> Self {
Self {
Expand Down
27 changes: 27 additions & 0 deletions crates/vm/src/dict_inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,33 @@ impl<T> Default for Dict<T> {
}
}

impl Dict {
// Initialize an unpublished, empty dict from unique interned names. This
// needs the process hash secret, but cannot call Python during bootstrap.
pub(crate) fn initialize_attributes(
&self,
attrs: crate::builtins::type_::PyAttributes,
secret: &hash::HashSecret,
) {
let mut inner = self.write();
assert_eq!(inner.used, 0);
inner.entries = attrs
.into_iter()
.map(|(key, value)| {
Some(DictEntry {
hash: secret.hash_bytes(key.as_bytes()),
key: key.to_owned().into(),
index: 0,
value,
})
})
.collect();
inner.used = inner.entries.len();
let size = inner.used * 2;
inner.resize(size);
}
}

#[derive(Clone)]
struct DictEntry<T> {
hash: HashValue,
Expand Down
4 changes: 2 additions & 2 deletions crates/vm/src/function/method.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
Context, Py, PyObjectRef, PyPayload, PyRef, VirtualMachine,
builtins::{
PyType,
PyStaticMethod, PyType,
builtin_func::{PyNativeFunction, PyNativeMethod},
descriptor::{PyClassMethodDescriptor, PyMethodDescriptor},
},
Expand Down Expand Up @@ -183,7 +183,7 @@ impl PyMethodDef {
} else if self.flags.contains(PyMethodFlags::CLASS) {
self.build_classmethod(ctx, class).into()
} else if self.flags.contains(PyMethodFlags::STATIC) {
self.build_staticmethod(ctx, class).into()
PyStaticMethod::new_native(self.build_staticmethod(ctx, class), class, ctx).into()
Comment thread
1ndahous3 marked this conversation as resolved.
} else {
unreachable!()
}
Expand Down
3 changes: 2 additions & 1 deletion crates/vm/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1047,13 +1047,14 @@ static HASH_SECRET: std::sync::OnceLock<HashSecret> = std::sync::OnceLock::new()
/// `hash_seed` is used only when the secret is not set yet. `None` draws a
/// random seed. A later call keeps the existing secret and ignores `hash_seed`.
pub(crate) fn init_hash_secret(hash_seed: Option<u32>) {
let _ = HASH_SECRET.get_or_init(|| {
let secret = HASH_SECRET.get_or_init(|| {
let seed = hash_seed.unwrap_or_else(|| {
// os_random is expensive, but this runs only once per process.
u32::from_ne_bytes(rustpython_common::rand::os_random())
});
HashSecret::new(seed)
});
builtins::PyStaticMethod::init_native_metadata(secret);
}

/// Process-wide `_Py_HashSecret` used for str/bytes hashing.
Expand Down
7 changes: 7 additions & 0 deletions extra_tests/snippets/builtin_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,3 +817,10 @@ class Diamond(FirstChild, SecondChild):
value = Diamond()
value.left = 43
assert value.left == 43

# Native static methods retain their descriptor in the class dictionary.
native_static = str.__dict__["maketrans"]
assert isinstance(native_static, staticmethod)
assert native_static.__func__ is str.maketrans
for name in ("__module__", "__name__", "__qualname__", "__doc__"):
assert getattr(native_static, name) == getattr(str.maketrans, name)
Loading