mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Compare commits
11 Commits
0.5.0
...
specializa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b061612e5 | ||
|
|
add34a2f19 | ||
|
|
e0886e2fb6 | ||
|
|
328de2e83e | ||
|
|
2d4e1f2f5e | ||
|
|
15836ca0fc | ||
|
|
09c3bb1d7f | ||
|
|
94e8d54731 | ||
|
|
382be9a525 | ||
|
|
77b46d53ca | ||
|
|
bcd618ecc9 |
@@ -61,6 +61,9 @@
|
|||||||
"dedents",
|
"dedents",
|
||||||
"deduped",
|
"deduped",
|
||||||
"deoptimize",
|
"deoptimize",
|
||||||
|
"downcastable",
|
||||||
|
"downcasted",
|
||||||
|
"dumpable",
|
||||||
"emscripten",
|
"emscripten",
|
||||||
"excs",
|
"excs",
|
||||||
"interps",
|
"interps",
|
||||||
|
|||||||
4
.github/workflows/cron-ci.yaml
vendored
4
.github/workflows/cron-ci.yaml
vendored
@@ -12,7 +12,7 @@ on:
|
|||||||
name: Periodic checks/tasks
|
name: Periodic checks/tasks
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,encodings,ssl-rustls,jit
|
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls,jit,host_env
|
||||||
PYTHON_VERSION: "3.14.3"
|
PYTHON_VERSION: "3.14.3"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- run: sudo apt-get update && sudo apt-get -y install lcov
|
- run: sudo apt-get update && sudo apt-get -y install lcov
|
||||||
- name: Run cargo-llvm-cov with Rust tests.
|
- name: Run cargo-llvm-cov with Rust tests.
|
||||||
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher --verbose --no-default-features --features stdlib,importlib,encodings,ssl-rustls,jit
|
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher --verbose --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls,jit
|
||||||
- name: Run cargo-llvm-cov with Python snippets.
|
- name: Run cargo-llvm-cov with Python snippets.
|
||||||
run: python scripts/cargo-llvm-cov.py
|
run: python scripts/cargo-llvm-cov.py
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|||||||
@@ -276,7 +276,6 @@ pub struct TypeSpecializationCache {
|
|||||||
pub init: PyAtomicRef<Option<PyFunction>>,
|
pub init: PyAtomicRef<Option<PyFunction>>,
|
||||||
pub getitem: PyAtomicRef<Option<PyFunction>>,
|
pub getitem: PyAtomicRef<Option<PyFunction>>,
|
||||||
pub getitem_version: AtomicU32,
|
pub getitem_version: AtomicU32,
|
||||||
// Serialize cache writes/invalidation similar to CPython's BEGIN_TYPE_LOCK.
|
|
||||||
write_lock: PyMutex<()>,
|
write_lock: PyMutex<()>,
|
||||||
retired: PyRwLock<Vec<PyObjectRef>>,
|
retired: PyRwLock<Vec<PyObjectRef>>,
|
||||||
}
|
}
|
||||||
@@ -302,9 +301,6 @@ impl TypeSpecializationCache {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn swap_init(&self, new_init: Option<PyRef<PyFunction>>, vm: Option<&VirtualMachine>) {
|
fn swap_init(&self, new_init: Option<PyRef<PyFunction>>, vm: Option<&VirtualMachine>) {
|
||||||
if let Some(vm) = vm {
|
if let Some(vm) = vm {
|
||||||
// Keep replaced refs alive for the currently executing frame, matching
|
|
||||||
// CPython-style "old pointer remains valid during ongoing execution"
|
|
||||||
// without accumulating global retired refs.
|
|
||||||
self.init.swap_to_temporary_refs(new_init, vm);
|
self.init.swap_to_temporary_refs(new_init, vm);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -329,8 +325,6 @@ impl TypeSpecializationCache {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn invalidate_for_type_modified(&self) {
|
fn invalidate_for_type_modified(&self) {
|
||||||
let _guard = self.write_lock.lock();
|
let _guard = self.write_lock.lock();
|
||||||
// _spec_cache contract: type modification invalidates all cached
|
|
||||||
// specialization functions.
|
|
||||||
self.swap_init(None, None);
|
self.swap_init(None, None);
|
||||||
self.swap_getitem(None, None);
|
self.swap_getitem(None, None);
|
||||||
}
|
}
|
||||||
@@ -457,9 +451,15 @@ fn is_subtype_with_mro(a_mro: &[PyTypeRef], a: &Py<PyType>, b: &Py<PyType>) -> b
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PyType {
|
impl PyType {
|
||||||
|
#[inline]
|
||||||
|
fn with_type_lock<R>(vm: &VirtualMachine, f: impl FnOnce() -> R) -> R {
|
||||||
|
let _guard = vm.state.type_mutex.lock();
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
/// Assign a fresh version tag. Returns 0 if the version counter has been
|
/// Assign a fresh version tag. Returns 0 if the version counter has been
|
||||||
/// exhausted, in which case no new cache entries can be created.
|
/// exhausted, in which case no new cache entries can be created.
|
||||||
pub fn assign_version_tag(&self) -> u32 {
|
fn assign_version_tag_inner(&self) -> u32 {
|
||||||
let v = self.tp_version_tag.load(Ordering::Acquire);
|
let v = self.tp_version_tag.load(Ordering::Acquire);
|
||||||
if v != 0 {
|
if v != 0 {
|
||||||
return v;
|
return v;
|
||||||
@@ -467,7 +467,7 @@ impl PyType {
|
|||||||
|
|
||||||
// Assign versions to all direct bases first (MRO invariant).
|
// Assign versions to all direct bases first (MRO invariant).
|
||||||
for base in self.bases.read().iter() {
|
for base in self.bases.read().iter() {
|
||||||
if base.assign_version_tag() == 0 {
|
if base.assign_version_tag_inner() == 0 {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -487,8 +487,23 @@ impl PyType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn assign_version_tag(&self) -> u32 {
|
||||||
|
self.assign_version_tag_inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn version_for_specialization(&self, vm: &VirtualMachine) -> u32 {
|
||||||
|
Self::with_type_lock(vm, || {
|
||||||
|
let version = self.tp_version_tag.load(Ordering::Acquire);
|
||||||
|
if version == 0 {
|
||||||
|
self.assign_version_tag_inner()
|
||||||
|
} else {
|
||||||
|
version
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Invalidate this type's version tag and cascade to all subclasses.
|
/// Invalidate this type's version tag and cascade to all subclasses.
|
||||||
pub fn modified(&self) {
|
fn modified_inner(&self) {
|
||||||
if let Some(ext) = self.heaptype_ext.as_ref() {
|
if let Some(ext) = self.heaptype_ext.as_ref() {
|
||||||
ext.specialization_cache.invalidate_for_type_modified();
|
ext.specialization_cache.invalidate_for_type_modified();
|
||||||
}
|
}
|
||||||
@@ -505,11 +520,15 @@ impl PyType {
|
|||||||
let subclasses = self.subclasses.read();
|
let subclasses = self.subclasses.read();
|
||||||
for weak_ref in subclasses.iter() {
|
for weak_ref in subclasses.iter() {
|
||||||
if let Some(sub) = weak_ref.upgrade() {
|
if let Some(sub) = weak_ref.upgrade() {
|
||||||
sub.downcast_ref::<PyType>().unwrap().modified();
|
sub.downcast_ref::<PyType>().unwrap().modified_inner();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn modified(&self) {
|
||||||
|
self.modified_inner();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_simple_heap(
|
pub fn new_simple_heap(
|
||||||
name: &str,
|
name: &str,
|
||||||
base: &Py<PyType>,
|
base: &Py<PyType>,
|
||||||
@@ -898,6 +917,74 @@ impl PyType {
|
|||||||
self.find_name_in_mro(attr_name)
|
self.find_name_in_mro(attr_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CPython-style `_PyType_LookupRefAndVersion` equivalent for interned names.
|
||||||
|
/// Returns the observed lookup result and the type version used for the lookup.
|
||||||
|
pub(crate) fn lookup_ref_and_version_interned(
|
||||||
|
&self,
|
||||||
|
name: &'static PyStrInterned,
|
||||||
|
vm: &VirtualMachine,
|
||||||
|
) -> (Option<PyObjectRef>, u32) {
|
||||||
|
let version = self.tp_version_tag.load(Ordering::Acquire);
|
||||||
|
if version != 0 {
|
||||||
|
let idx = type_cache_hash(version, name);
|
||||||
|
let entry = &TYPE_CACHE[idx];
|
||||||
|
let name_ptr = name as *const _ as *mut _;
|
||||||
|
loop {
|
||||||
|
let seq1 = entry.begin_read();
|
||||||
|
let entry_version = entry.version.load(Ordering::Acquire);
|
||||||
|
let type_version = self.tp_version_tag.load(Ordering::Acquire);
|
||||||
|
if entry_version != type_version
|
||||||
|
|| !core::ptr::eq(entry.name.load(Ordering::Relaxed), name_ptr)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let ptr = entry.value.load(Ordering::Acquire);
|
||||||
|
if ptr.is_null() {
|
||||||
|
if entry.end_read(seq1) {
|
||||||
|
return (None, entry_version);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(cloned) = unsafe { PyObject::try_to_owned_from_ptr(ptr) } {
|
||||||
|
let same_ptr = core::ptr::eq(entry.value.load(Ordering::Relaxed), ptr);
|
||||||
|
if same_ptr && entry.end_read(seq1) {
|
||||||
|
return (Some(cloned), entry_version);
|
||||||
|
}
|
||||||
|
drop(cloned);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::with_type_lock(vm, || {
|
||||||
|
let assigned = if self.tp_version_tag.load(Ordering::Acquire) == 0 {
|
||||||
|
self.assign_version_tag_inner()
|
||||||
|
} else {
|
||||||
|
self.tp_version_tag.load(Ordering::Acquire)
|
||||||
|
};
|
||||||
|
let result = self.find_name_in_mro_uncached(name);
|
||||||
|
if assigned != 0
|
||||||
|
&& !TYPE_CACHE_CLEARING.load(Ordering::Acquire)
|
||||||
|
&& self.tp_version_tag.load(Ordering::Acquire) == assigned
|
||||||
|
{
|
||||||
|
let idx = type_cache_hash(assigned, name);
|
||||||
|
let entry = &TYPE_CACHE[idx];
|
||||||
|
let name_ptr = name as *const _ as *mut _;
|
||||||
|
entry.begin_write();
|
||||||
|
entry.version.store(0, Ordering::Release);
|
||||||
|
let new_ptr = result.as_ref().map_or(core::ptr::null_mut(), |found| {
|
||||||
|
&**found as *const PyObject as *mut _
|
||||||
|
});
|
||||||
|
entry.value.store(new_ptr, Ordering::Relaxed);
|
||||||
|
entry.name.store(name_ptr, Ordering::Relaxed);
|
||||||
|
entry.version.store(assigned, Ordering::Release);
|
||||||
|
entry.end_write();
|
||||||
|
}
|
||||||
|
(result, assigned)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Cache __init__ for CALL_ALLOC_AND_ENTER_INIT specialization.
|
/// Cache __init__ for CALL_ALLOC_AND_ENTER_INIT specialization.
|
||||||
/// The cache is valid only when guarded by the type version check.
|
/// The cache is valid only when guarded by the type version check.
|
||||||
pub(crate) fn cache_init_for_specialization(
|
pub(crate) fn cache_init_for_specialization(
|
||||||
@@ -912,6 +999,7 @@ impl PyType {
|
|||||||
if tp_version == 0 {
|
if tp_version == 0 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Self::with_type_lock(vm, || {
|
||||||
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
|
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -921,6 +1009,7 @@ impl PyType {
|
|||||||
}
|
}
|
||||||
ext.specialization_cache.swap_init(Some(init), Some(vm));
|
ext.specialization_cache.swap_init(Some(init), Some(vm));
|
||||||
true
|
true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read cached __init__ for CALL_ALLOC_AND_ENTER_INIT specialization.
|
/// Read cached __init__ for CALL_ALLOC_AND_ENTER_INIT specialization.
|
||||||
@@ -954,6 +1043,7 @@ impl PyType {
|
|||||||
if tp_version == 0 {
|
if tp_version == 0 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Self::with_type_lock(vm, || {
|
||||||
let _guard = ext.specialization_cache.write_lock.lock();
|
let _guard = ext.specialization_cache.write_lock.lock();
|
||||||
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
|
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
|
||||||
return false;
|
return false;
|
||||||
@@ -962,18 +1052,18 @@ impl PyType {
|
|||||||
if func_version == 0 {
|
if func_version == 0 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ext.specialization_cache
|
|
||||||
.swap_getitem(Some(getitem), Some(vm));
|
|
||||||
ext.specialization_cache
|
ext.specialization_cache
|
||||||
.getitem_version
|
.getitem_version
|
||||||
.store(func_version, Ordering::Relaxed);
|
.store(func_version, Ordering::Release);
|
||||||
|
ext.specialization_cache
|
||||||
|
.swap_getitem(Some(getitem), Some(vm));
|
||||||
true
|
true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read cached __getitem__ for BINARY_OP_SUBSCR_GETITEM specialization.
|
/// Read cached __getitem__ for BINARY_OP_SUBSCR_GETITEM specialization.
|
||||||
pub(crate) fn get_cached_getitem_for_specialization(&self) -> Option<(PyRef<PyFunction>, u32)> {
|
pub(crate) fn get_cached_getitem_for_specialization(&self) -> Option<(PyRef<PyFunction>, u32)> {
|
||||||
let ext = self.heaptype_ext.as_ref()?;
|
let ext = self.heaptype_ext.as_ref()?;
|
||||||
// Match CPython check order: pointer (Acquire) then function version.
|
|
||||||
let getitem = ext
|
let getitem = ext
|
||||||
.specialization_cache
|
.specialization_cache
|
||||||
.getitem
|
.getitem
|
||||||
@@ -981,7 +1071,7 @@ impl PyType {
|
|||||||
let cached_version = ext
|
let cached_version = ext
|
||||||
.specialization_cache
|
.specialization_cache
|
||||||
.getitem_version
|
.getitem_version
|
||||||
.load(Ordering::Relaxed);
|
.load(Ordering::Acquire);
|
||||||
if cached_version == 0 {
|
if cached_version == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -1334,6 +1424,7 @@ impl PyType {
|
|||||||
// // TODO: how to uniquely identify the subclasses to remove?
|
// // TODO: how to uniquely identify the subclasses to remove?
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
Self::with_type_lock(vm, || {
|
||||||
*zelf.bases.write() = bases;
|
*zelf.bases.write() = bases;
|
||||||
// Recursively update the mros of this class and all subclasses
|
// Recursively update the mros of this class and all subclasses
|
||||||
fn update_mro_recursively(cls: &PyType, vm: &VirtualMachine) -> PyResult<()> {
|
fn update_mro_recursively(cls: &PyType, vm: &VirtualMachine) -> PyResult<()> {
|
||||||
@@ -1352,7 +1443,7 @@ impl PyType {
|
|||||||
update_mro_recursively(zelf, vm)?;
|
update_mro_recursively(zelf, vm)?;
|
||||||
|
|
||||||
// Invalidate inline caches
|
// Invalidate inline caches
|
||||||
zelf.modified();
|
zelf.modified_inner();
|
||||||
|
|
||||||
// TODO: do any old slots need to be cleaned up first?
|
// TODO: do any old slots need to be cleaned up first?
|
||||||
zelf.init_slots(&vm.ctx);
|
zelf.init_slots(&vm.ctx);
|
||||||
@@ -1366,6 +1457,8 @@ impl PyType {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1457,20 +1550,31 @@ impl PyType {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut attrs = self.attributes.write();
|
let annotate_key = identifier!(vm, __annotate__);
|
||||||
// First try __annotate__, in case that's been set explicitly
|
let annotate_func_key = identifier!(vm, __annotate_func__);
|
||||||
if let Some(annotate) = attrs.get(identifier!(vm, __annotate__)).cloned() {
|
let attrs = self.attributes.read();
|
||||||
|
if let Some(annotate) = attrs.get(annotate_key).cloned() {
|
||||||
return Ok(annotate);
|
return Ok(annotate);
|
||||||
}
|
}
|
||||||
// Then try __annotate_func__
|
if let Some(annotate) = attrs.get(annotate_func_key).cloned() {
|
||||||
if let Some(annotate) = attrs.get(identifier!(vm, __annotate_func__)).cloned() {
|
|
||||||
// TODO: Apply descriptor tp_descr_get if needed
|
|
||||||
return Ok(annotate);
|
return Ok(annotate);
|
||||||
}
|
}
|
||||||
// Set __annotate_func__ = None and return None
|
drop(attrs);
|
||||||
|
|
||||||
let none = vm.ctx.none();
|
let none = vm.ctx.none();
|
||||||
attrs.insert(identifier!(vm, __annotate_func__), none.clone());
|
let (result, _prev) = Self::with_type_lock(vm, || {
|
||||||
Ok(none)
|
let mut attrs = self.attributes.write();
|
||||||
|
if let Some(annotate) = attrs.get(annotate_key).cloned() {
|
||||||
|
return (annotate, None);
|
||||||
|
}
|
||||||
|
if let Some(annotate) = attrs.get(annotate_func_key).cloned() {
|
||||||
|
return (annotate, None);
|
||||||
|
}
|
||||||
|
self.modified_inner();
|
||||||
|
let prev = attrs.insert(annotate_func_key, none.clone());
|
||||||
|
(none, prev)
|
||||||
|
});
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pygetset(setter)]
|
#[pygetset(setter)]
|
||||||
@@ -1493,20 +1597,27 @@ impl PyType {
|
|||||||
return Err(vm.new_type_error("__annotate__ must be callable or None"));
|
return Err(vm.new_type_error("__annotate__ must be callable or None"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _prev_values = Self::with_type_lock(vm, || {
|
||||||
|
self.modified_inner();
|
||||||
let mut attrs = self.attributes.write();
|
let mut attrs = self.attributes.write();
|
||||||
// Clear cached annotations only when setting to a new callable
|
let removed = if !vm.is_none(&value) {
|
||||||
if !vm.is_none(&value) {
|
attrs.swap_remove(identifier!(vm, __annotations_cache__))
|
||||||
attrs.swap_remove(identifier!(vm, __annotations_cache__));
|
} else {
|
||||||
}
|
None
|
||||||
attrs.insert(identifier!(vm, __annotate_func__), value.clone());
|
};
|
||||||
|
let prev = attrs.insert(identifier!(vm, __annotate_func__), value);
|
||||||
|
(removed, prev)
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pygetset]
|
#[pygetset]
|
||||||
fn __annotations__(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
fn __annotations__(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||||
|
let annotations_key = identifier!(vm, __annotations__);
|
||||||
|
let annotations_cache_key = identifier!(vm, __annotations_cache__);
|
||||||
let attrs = self.attributes.read();
|
let attrs = self.attributes.read();
|
||||||
if let Some(annotations) = attrs.get(identifier!(vm, __annotations__)).cloned() {
|
if let Some(annotations) = attrs.get(annotations_key).cloned() {
|
||||||
// Ignore the __annotations__ descriptor stored on type itself.
|
// Ignore the __annotations__ descriptor stored on type itself.
|
||||||
if !annotations.class().is(vm.ctx.types.getset_type) {
|
if !annotations.class().is(vm.ctx.types.getset_type) {
|
||||||
if vm.is_none(&annotations)
|
if vm.is_none(&annotations)
|
||||||
@@ -1521,8 +1632,7 @@ impl PyType {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Then try __annotations_cache__
|
if let Some(annotations) = attrs.get(annotations_cache_key).cloned() {
|
||||||
if let Some(annotations) = attrs.get(identifier!(vm, __annotations_cache__)).cloned() {
|
|
||||||
if vm.is_none(&annotations)
|
if vm.is_none(&annotations)
|
||||||
|| annotations.class().is(vm.ctx.types.dict_type)
|
|| annotations.class().is(vm.ctx.types.dict_type)
|
||||||
|| self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
|
|| self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
|
||||||
@@ -1559,11 +1669,21 @@ impl PyType {
|
|||||||
vm.ctx.new_dict().into()
|
vm.ctx.new_dict().into()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cache the result in __annotations_cache__
|
let (result, _prev) = Self::with_type_lock(vm, || {
|
||||||
self.attributes
|
let mut attrs = self.attributes.write();
|
||||||
.write()
|
if let Some(existing) = attrs.get(annotations_key).cloned()
|
||||||
.insert(identifier!(vm, __annotations_cache__), annotations.clone());
|
&& !existing.class().is(vm.ctx.types.getset_type)
|
||||||
Ok(annotations)
|
{
|
||||||
|
return (existing, None);
|
||||||
|
}
|
||||||
|
if let Some(existing) = attrs.get(annotations_cache_key).cloned() {
|
||||||
|
return (existing, None);
|
||||||
|
}
|
||||||
|
self.modified_inner();
|
||||||
|
let prev = attrs.insert(annotations_cache_key, annotations.clone());
|
||||||
|
(annotations, prev)
|
||||||
|
});
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pygetset(setter)]
|
#[pygetset(setter)]
|
||||||
@@ -1579,43 +1699,43 @@ impl PyType {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _prev_values = Self::with_type_lock(vm, || {
|
||||||
|
self.modified_inner();
|
||||||
let mut attrs = self.attributes.write();
|
let mut attrs = self.attributes.write();
|
||||||
let has_annotations = attrs.contains_key(identifier!(vm, __annotations__));
|
let has_annotations = attrs.contains_key(identifier!(vm, __annotations__));
|
||||||
|
|
||||||
|
let mut prev = Vec::new();
|
||||||
match value {
|
match value {
|
||||||
crate::function::PySetterValue::Assign(value) => {
|
crate::function::PySetterValue::Assign(value) => {
|
||||||
// SET path: store the value (including None)
|
|
||||||
let key = if has_annotations {
|
let key = if has_annotations {
|
||||||
identifier!(vm, __annotations__)
|
identifier!(vm, __annotations__)
|
||||||
} else {
|
} else {
|
||||||
identifier!(vm, __annotations_cache__)
|
identifier!(vm, __annotations_cache__)
|
||||||
};
|
};
|
||||||
attrs.insert(key, value);
|
prev.extend(attrs.insert(key, value));
|
||||||
if has_annotations {
|
if has_annotations {
|
||||||
attrs.swap_remove(identifier!(vm, __annotations_cache__));
|
prev.extend(attrs.swap_remove(identifier!(vm, __annotations_cache__)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crate::function::PySetterValue::Delete => {
|
crate::function::PySetterValue::Delete => {
|
||||||
// DELETE path: remove the key
|
|
||||||
let removed = if has_annotations {
|
let removed = if has_annotations {
|
||||||
attrs
|
attrs.swap_remove(identifier!(vm, __annotations__))
|
||||||
.swap_remove(identifier!(vm, __annotations__))
|
|
||||||
.is_some()
|
|
||||||
} else {
|
} else {
|
||||||
attrs
|
attrs.swap_remove(identifier!(vm, __annotations_cache__))
|
||||||
.swap_remove(identifier!(vm, __annotations_cache__))
|
|
||||||
.is_some()
|
|
||||||
};
|
};
|
||||||
if !removed {
|
if removed.is_none() {
|
||||||
return Err(vm.new_attribute_error("__annotations__"));
|
return Err(vm.new_attribute_error("__annotations__"));
|
||||||
}
|
}
|
||||||
|
prev.extend(removed);
|
||||||
if has_annotations {
|
if has_annotations {
|
||||||
attrs.swap_remove(identifier!(vm, __annotations_cache__));
|
prev.extend(attrs.swap_remove(identifier!(vm, __annotations_cache__)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
attrs.swap_remove(identifier!(vm, __annotate_func__));
|
prev.extend(attrs.swap_remove(identifier!(vm, __annotate_func__)));
|
||||||
attrs.swap_remove(identifier!(vm, __annotate__));
|
prev.extend(attrs.swap_remove(identifier!(vm, __annotate__)));
|
||||||
|
Ok(prev)
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1648,9 +1768,13 @@ impl PyType {
|
|||||||
#[pygetset(setter)]
|
#[pygetset(setter)]
|
||||||
fn set___module__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
fn set___module__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||||
self.check_set_special_type_attr(identifier!(vm, __module__), vm)?;
|
self.check_set_special_type_attr(identifier!(vm, __module__), vm)?;
|
||||||
|
let _prev_values = Self::with_type_lock(vm, || {
|
||||||
|
self.modified_inner();
|
||||||
let mut attributes = self.attributes.write();
|
let mut attributes = self.attributes.write();
|
||||||
attributes.swap_remove(identifier!(vm, __firstlineno__));
|
let removed = attributes.swap_remove(identifier!(vm, __firstlineno__));
|
||||||
attributes.insert(identifier!(vm, __module__), value);
|
let prev = attributes.insert(identifier!(vm, __module__), value);
|
||||||
|
(removed, prev)
|
||||||
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1772,24 +1896,26 @@ impl PyType {
|
|||||||
value: PySetterValue<PyTupleRef>,
|
value: PySetterValue<PyTupleRef>,
|
||||||
vm: &VirtualMachine,
|
vm: &VirtualMachine,
|
||||||
) -> PyResult<()> {
|
) -> PyResult<()> {
|
||||||
match value {
|
|
||||||
PySetterValue::Assign(ref val) => {
|
|
||||||
let key = identifier!(vm, __type_params__);
|
let key = identifier!(vm, __type_params__);
|
||||||
|
match value {
|
||||||
|
PySetterValue::Assign(val) => {
|
||||||
self.check_set_special_type_attr(key, vm)?;
|
self.check_set_special_type_attr(key, vm)?;
|
||||||
self.modified();
|
let _prev_value = Self::with_type_lock(vm, || {
|
||||||
self.attributes.write().insert(key, val.clone().into());
|
self.modified_inner();
|
||||||
|
self.attributes.write().insert(key, val.into())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
PySetterValue::Delete => {
|
PySetterValue::Delete => {
|
||||||
// For delete, we still need to check if the type is immutable
|
|
||||||
if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
|
if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
|
||||||
return Err(vm.new_type_error(format!(
|
return Err(vm.new_type_error(format!(
|
||||||
"cannot delete '__type_params__' attribute of immutable type '{}'",
|
"cannot delete '__type_params__' attribute of immutable type '{}'",
|
||||||
self.slot_name()
|
self.slot_name()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let key = identifier!(vm, __type_params__);
|
let _prev_value = Self::with_type_lock(vm, || {
|
||||||
self.modified();
|
self.modified_inner();
|
||||||
self.attributes.write().shift_remove(&key);
|
self.attributes.write().shift_remove(&key)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -2397,10 +2523,12 @@ impl Py<PyType> {
|
|||||||
// Check if we can set this special type attribute
|
// Check if we can set this special type attribute
|
||||||
self.check_set_special_type_attr(identifier!(vm, __doc__), vm)?;
|
self.check_set_special_type_attr(identifier!(vm, __doc__), vm)?;
|
||||||
|
|
||||||
// Set the __doc__ in the type's dict
|
let _prev_value = PyType::with_type_lock(vm, || {
|
||||||
|
self.modified_inner();
|
||||||
self.attributes
|
self.attributes
|
||||||
.write()
|
.write()
|
||||||
.insert(identifier!(vm, __doc__), value);
|
.insert(identifier!(vm, __doc__), value)
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -2462,13 +2590,17 @@ impl SetAttr for PyType {
|
|||||||
}
|
}
|
||||||
let assign = value.is_assign();
|
let assign = value.is_assign();
|
||||||
|
|
||||||
|
// Drop old value OUTSIDE the type lock to avoid deadlock:
|
||||||
|
// dropping may trigger weakref callbacks → method calls →
|
||||||
|
// LOAD_ATTR specialization → version_for_specialization → type lock.
|
||||||
|
let _prev_value = Self::with_type_lock(vm, || {
|
||||||
// Invalidate inline caches before modifying attributes.
|
// Invalidate inline caches before modifying attributes.
|
||||||
// This ensures other threads see the version invalidation before
|
// This ensures other threads see the version invalidation before
|
||||||
// any attribute changes, preventing use-after-free of cached descriptors.
|
// any attribute changes, preventing use-after-free of cached descriptors.
|
||||||
zelf.modified();
|
zelf.modified_inner();
|
||||||
|
|
||||||
if let PySetterValue::Assign(value) = value {
|
if let PySetterValue::Assign(value) = value {
|
||||||
zelf.attributes.write().insert(attr_name, value);
|
Ok(zelf.attributes.write().insert(attr_name, value))
|
||||||
} else {
|
} else {
|
||||||
let prev_value = zelf.attributes.write().shift_remove(attr_name); // TODO: swap_remove applicable?
|
let prev_value = zelf.attributes.write().shift_remove(attr_name); // TODO: swap_remove applicable?
|
||||||
if prev_value.is_none() {
|
if prev_value.is_none() {
|
||||||
@@ -2478,7 +2610,9 @@ impl SetAttr for PyType {
|
|||||||
attr_name,
|
attr_name,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
Ok(prev_value)
|
||||||
}
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
if attr_name.as_wtf8().starts_with("__") && attr_name.as_wtf8().ends_with("__") {
|
if attr_name.as_wtf8().starts_with("__") && attr_name.as_wtf8().ends_with("__") {
|
||||||
if assign {
|
if assign {
|
||||||
|
|||||||
@@ -1051,7 +1051,7 @@ struct ExecutingFrame<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn specialization_compact_int_value(i: &PyInt, vm: &VirtualMachine) -> Option<isize> {
|
fn cpython_compact_int_value(i: &PyInt, vm: &VirtualMachine) -> Option<isize> {
|
||||||
// _PyLong_IsCompact(): a one-digit PyLong (base 2^30),
|
// _PyLong_IsCompact(): a one-digit PyLong (base 2^30),
|
||||||
// i.e. abs(value) <= 2^30 - 1.
|
// i.e. abs(value) <= 2^30 - 1.
|
||||||
const CPYTHON_COMPACT_LONG_ABS_MAX: i64 = (1i64 << 30) - 1;
|
const CPYTHON_COMPACT_LONG_ABS_MAX: i64 = (1i64 << 30) - 1;
|
||||||
@@ -1066,7 +1066,7 @@ fn specialization_compact_int_value(i: &PyInt, vm: &VirtualMachine) -> Option<is
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn compact_int_from_obj(obj: &PyObject, vm: &VirtualMachine) -> Option<isize> {
|
fn compact_int_from_obj(obj: &PyObject, vm: &VirtualMachine) -> Option<isize> {
|
||||||
obj.downcast_ref_if_exact::<PyInt>(vm)
|
obj.downcast_ref_if_exact::<PyInt>(vm)
|
||||||
.and_then(|i| specialization_compact_int_value(i, vm))
|
.and_then(|i| cpython_compact_int_value(i, vm))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -1075,7 +1075,7 @@ fn exact_float_from_obj(obj: &PyObject, vm: &VirtualMachine) -> Option<f64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn specialization_nonnegative_compact_index(i: &PyInt, vm: &VirtualMachine) -> Option<usize> {
|
fn cpython_nonnegative_compact_index(i: &PyInt, vm: &VirtualMachine) -> Option<usize> {
|
||||||
// _PyLong_IsNonNegativeCompact(): a single base-2^30 digit.
|
// _PyLong_IsNonNegativeCompact(): a single base-2^30 digit.
|
||||||
const CPYTHON_COMPACT_LONG_MAX: u64 = (1u64 << 30) - 1;
|
const CPYTHON_COMPACT_LONG_MAX: u64 = (1u64 << 30) - 1;
|
||||||
let v = i.try_to_primitive::<u64>(vm).ok()?;
|
let v = i.try_to_primitive::<u64>(vm).ok()?;
|
||||||
@@ -3999,7 +3999,7 @@ impl ExecutingFrame<'_> {
|
|||||||
let value = self.pop_value();
|
let value = self.pop_value();
|
||||||
if let Some(list) = obj.downcast_ref_if_exact::<PyList>(vm)
|
if let Some(list) = obj.downcast_ref_if_exact::<PyList>(vm)
|
||||||
&& let Some(int_idx) = idx.downcast_ref_if_exact::<PyInt>(vm)
|
&& let Some(int_idx) = idx.downcast_ref_if_exact::<PyInt>(vm)
|
||||||
&& let Some(i) = specialization_nonnegative_compact_index(int_idx, vm)
|
&& let Some(i) = cpython_nonnegative_compact_index(int_idx, vm)
|
||||||
{
|
{
|
||||||
let mut vec = list.borrow_vec_mut();
|
let mut vec = list.borrow_vec_mut();
|
||||||
if i < vec.len() {
|
if i < vec.len() {
|
||||||
@@ -4099,7 +4099,7 @@ impl ExecutingFrame<'_> {
|
|||||||
if let (Some(list), Some(idx)) = (
|
if let (Some(list), Some(idx)) = (
|
||||||
a.downcast_ref_if_exact::<PyList>(vm),
|
a.downcast_ref_if_exact::<PyList>(vm),
|
||||||
b.downcast_ref_if_exact::<PyInt>(vm),
|
b.downcast_ref_if_exact::<PyInt>(vm),
|
||||||
) && let Some(i) = specialization_nonnegative_compact_index(idx, vm)
|
) && let Some(i) = cpython_nonnegative_compact_index(idx, vm)
|
||||||
{
|
{
|
||||||
let vec = list.borrow_vec();
|
let vec = list.borrow_vec();
|
||||||
if i < vec.len() {
|
if i < vec.len() {
|
||||||
@@ -4119,7 +4119,7 @@ impl ExecutingFrame<'_> {
|
|||||||
if let (Some(tuple), Some(idx)) = (
|
if let (Some(tuple), Some(idx)) = (
|
||||||
a.downcast_ref_if_exact::<PyTuple>(vm),
|
a.downcast_ref_if_exact::<PyTuple>(vm),
|
||||||
b.downcast_ref_if_exact::<PyInt>(vm),
|
b.downcast_ref_if_exact::<PyInt>(vm),
|
||||||
) && let Some(i) = specialization_nonnegative_compact_index(idx, vm)
|
) && let Some(i) = cpython_nonnegative_compact_index(idx, vm)
|
||||||
{
|
{
|
||||||
let elements = tuple.as_slice();
|
let elements = tuple.as_slice();
|
||||||
if i < elements.len() {
|
if i < elements.len() {
|
||||||
@@ -4161,7 +4161,7 @@ impl ExecutingFrame<'_> {
|
|||||||
if let (Some(a_str), Some(b_int)) = (
|
if let (Some(a_str), Some(b_int)) = (
|
||||||
a.downcast_ref_if_exact::<PyStr>(vm),
|
a.downcast_ref_if_exact::<PyStr>(vm),
|
||||||
b.downcast_ref_if_exact::<PyInt>(vm),
|
b.downcast_ref_if_exact::<PyInt>(vm),
|
||||||
) && let Some(i) = specialization_nonnegative_compact_index(b_int, vm)
|
) && let Some(i) = cpython_nonnegative_compact_index(b_int, vm)
|
||||||
&& let Ok(ch) = a_str.getitem_by_index(vm, i as isize)
|
&& let Ok(ch) = a_str.getitem_by_index(vm, i as isize)
|
||||||
&& ch.is_ascii()
|
&& ch.is_ascii()
|
||||||
{
|
{
|
||||||
@@ -4774,9 +4774,6 @@ impl ExecutingFrame<'_> {
|
|||||||
&& let Some(init_func) = cls.get_cached_init_for_specialization(cached_version)
|
&& let Some(init_func) = cls.get_cached_init_for_specialization(cached_version)
|
||||||
&& let Some(cls_alloc) = cls.slots.alloc.load()
|
&& let Some(cls_alloc) = cls.slots.alloc.load()
|
||||||
{
|
{
|
||||||
// Match CPython's `code->co_framesize + _Py_InitCleanup.co_framesize`
|
|
||||||
// shape, using RustPython's datastack-backed frame size
|
|
||||||
// equivalent for the extra shim frame.
|
|
||||||
let init_cleanup_stack_bytes =
|
let init_cleanup_stack_bytes =
|
||||||
datastack_frame_size_bytes_for_code(&vm.ctx.init_cleanup_code)
|
datastack_frame_size_bytes_for_code(&vm.ctx.init_cleanup_code)
|
||||||
.expect("_Py_InitCleanup shim is not a generator/coroutine");
|
.expect("_Py_InitCleanup shim is not a generator/coroutine");
|
||||||
@@ -4787,9 +4784,8 @@ impl ExecutingFrame<'_> {
|
|||||||
) {
|
) {
|
||||||
return self.execute_call_vectorcall(nargs, vm);
|
return self.execute_call_vectorcall(nargs, vm);
|
||||||
}
|
}
|
||||||
// CPython creates `_Py_InitCleanup` + `__init__` frames here.
|
// Two frames are created: `_Py_InitCleanup` + `__init__`.
|
||||||
// Keep the guard conservative and deopt when the effective
|
// Guard recursion limit accordingly and fall back.
|
||||||
// recursion budget for those two frames is not available.
|
|
||||||
if self.specialization_call_recursion_guard_with_extra_frames(vm, 1) {
|
if self.specialization_call_recursion_guard_with_extra_frames(vm, 1) {
|
||||||
return self.execute_call_vectorcall(nargs, vm);
|
return self.execute_call_vectorcall(nargs, vm);
|
||||||
}
|
}
|
||||||
@@ -5191,8 +5187,8 @@ impl ExecutingFrame<'_> {
|
|||||||
a.downcast_ref_if_exact::<PyInt>(vm),
|
a.downcast_ref_if_exact::<PyInt>(vm),
|
||||||
b.downcast_ref_if_exact::<PyInt>(vm),
|
b.downcast_ref_if_exact::<PyInt>(vm),
|
||||||
) && let (Some(a_val), Some(b_val)) = (
|
) && let (Some(a_val), Some(b_val)) = (
|
||||||
specialization_compact_int_value(a_int, vm),
|
cpython_compact_int_value(a_int, vm),
|
||||||
specialization_compact_int_value(b_int, vm),
|
cpython_compact_int_value(b_int, vm),
|
||||||
) {
|
) {
|
||||||
let op = self.compare_op_from_arg(arg);
|
let op = self.compare_op_from_arg(arg);
|
||||||
let result = op.eval_ord(a_val.cmp(&b_val));
|
let result = op.eval_ord(a_val.cmp(&b_val));
|
||||||
@@ -7320,10 +7316,7 @@ impl ExecutingFrame<'_> {
|
|||||||
.load()
|
.load()
|
||||||
.is_some_and(|f| f as usize == PyBaseObject::getattro as *const () as usize);
|
.is_some_and(|f| f as usize == PyBaseObject::getattro as *const () as usize);
|
||||||
if !is_default_getattro {
|
if !is_default_getattro {
|
||||||
let mut type_version = cls.tp_version_tag.load(Acquire);
|
let type_version = cls.version_for_specialization(_vm);
|
||||||
if type_version == 0 {
|
|
||||||
type_version = cls.assign_version_tag();
|
|
||||||
}
|
|
||||||
if type_version != 0
|
if type_version != 0
|
||||||
&& !oparg.is_method()
|
&& !oparg.is_method()
|
||||||
&& !self.specialization_eval_frame_active(_vm)
|
&& !self.specialization_eval_frame_active(_vm)
|
||||||
@@ -7361,10 +7354,7 @@ impl ExecutingFrame<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get or assign type version
|
// Get or assign type version
|
||||||
let mut type_version = cls.tp_version_tag.load(Acquire);
|
let type_version = cls.version_for_specialization(_vm);
|
||||||
if type_version == 0 {
|
|
||||||
type_version = cls.assign_version_tag();
|
|
||||||
}
|
|
||||||
if type_version == 0 {
|
if type_version == 0 {
|
||||||
// Version counter overflow — backoff to avoid re-attempting every execution
|
// Version counter overflow — backoff to avoid re-attempting every execution
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -7584,10 +7574,7 @@ impl ExecutingFrame<'_> {
|
|||||||
let owner_type = obj.downcast_ref::<PyType>().unwrap();
|
let owner_type = obj.downcast_ref::<PyType>().unwrap();
|
||||||
|
|
||||||
// Get or assign type version for the type object itself
|
// Get or assign type version for the type object itself
|
||||||
let mut type_version = owner_type.tp_version_tag.load(Acquire);
|
let type_version = owner_type.version_for_specialization(_vm);
|
||||||
if type_version == 0 {
|
|
||||||
type_version = owner_type.assign_version_tag();
|
|
||||||
}
|
|
||||||
if type_version == 0 {
|
if type_version == 0 {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.code.instructions.write_adaptive_counter(
|
self.code.instructions.write_adaptive_counter(
|
||||||
@@ -7622,10 +7609,7 @@ impl ExecutingFrame<'_> {
|
|||||||
}
|
}
|
||||||
let mut metaclass_version = 0;
|
let mut metaclass_version = 0;
|
||||||
if !mcl.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
|
if !mcl.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
|
||||||
metaclass_version = mcl.tp_version_tag.load(Acquire);
|
metaclass_version = mcl.version_for_specialization(_vm);
|
||||||
if metaclass_version == 0 {
|
|
||||||
metaclass_version = mcl.assign_version_tag();
|
|
||||||
}
|
|
||||||
if metaclass_version == 0 {
|
if metaclass_version == 0 {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.code.instructions.write_adaptive_counter(
|
self.code.instructions.write_adaptive_counter(
|
||||||
@@ -7797,7 +7781,7 @@ impl ExecutingFrame<'_> {
|
|||||||
bytecode::BinaryOperator::Subscr => {
|
bytecode::BinaryOperator::Subscr => {
|
||||||
let b_is_nonnegative_int = b
|
let b_is_nonnegative_int = b
|
||||||
.downcast_ref_if_exact::<PyInt>(vm)
|
.downcast_ref_if_exact::<PyInt>(vm)
|
||||||
.is_some_and(|i| specialization_nonnegative_compact_index(i, vm).is_some());
|
.is_some_and(|i| cpython_nonnegative_compact_index(i, vm).is_some());
|
||||||
if a.downcast_ref_if_exact::<PyList>(vm).is_some() && b_is_nonnegative_int {
|
if a.downcast_ref_if_exact::<PyList>(vm).is_some() && b_is_nonnegative_int {
|
||||||
Some(Instruction::BinaryOpSubscrListInt)
|
Some(Instruction::BinaryOpSubscrListInt)
|
||||||
} else if a.downcast_ref_if_exact::<PyTuple>(vm).is_some() && b_is_nonnegative_int {
|
} else if a.downcast_ref_if_exact::<PyTuple>(vm).is_some() && b_is_nonnegative_int {
|
||||||
@@ -7812,16 +7796,14 @@ impl ExecutingFrame<'_> {
|
|||||||
Some(Instruction::BinaryOpSubscrListSlice)
|
Some(Instruction::BinaryOpSubscrListSlice)
|
||||||
} else {
|
} else {
|
||||||
let cls = a.class();
|
let cls = a.class();
|
||||||
|
let (getitem, type_version) =
|
||||||
|
cls.lookup_ref_and_version_interned(identifier!(vm, __getitem__), vm);
|
||||||
if cls.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
|
if cls.slots.flags.has_feature(PyTypeFlags::HEAPTYPE)
|
||||||
&& !self.specialization_eval_frame_active(vm)
|
&& !self.specialization_eval_frame_active(vm)
|
||||||
&& let Some(_getitem) = cls.get_attr(identifier!(vm, __getitem__))
|
&& let Some(_getitem) = getitem
|
||||||
&& let Some(func) = _getitem.downcast_ref_if_exact::<PyFunction>(vm)
|
&& let Some(func) = _getitem.downcast_ref_if_exact::<PyFunction>(vm)
|
||||||
&& func.can_specialize_call(2)
|
&& func.can_specialize_call(2)
|
||||||
{
|
{
|
||||||
let mut type_version = cls.tp_version_tag.load(Acquire);
|
|
||||||
if type_version == 0 {
|
|
||||||
type_version = cls.assign_version_tag();
|
|
||||||
}
|
|
||||||
if type_version != 0 {
|
if type_version != 0 {
|
||||||
if cls.cache_getitem_for_specialization(
|
if cls.cache_getitem_for_specialization(
|
||||||
func.to_owned(),
|
func.to_owned(),
|
||||||
@@ -8360,11 +8342,8 @@ impl ExecutingFrame<'_> {
|
|||||||
&& cls_new_fn as usize == obj_new_fn as usize
|
&& cls_new_fn as usize == obj_new_fn as usize
|
||||||
&& cls_alloc_fn as usize == obj_alloc_fn as usize
|
&& cls_alloc_fn as usize == obj_alloc_fn as usize
|
||||||
{
|
{
|
||||||
let init = cls.get_attr(identifier!(vm, __init__));
|
let (init, version) =
|
||||||
let mut version = cls.tp_version_tag.load(Acquire);
|
cls.lookup_ref_and_version_interned(identifier!(vm, __init__), vm);
|
||||||
if version == 0 {
|
|
||||||
version = cls.assign_version_tag();
|
|
||||||
}
|
|
||||||
if version == 0 {
|
if version == 0 {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.code.instructions.write_adaptive_counter(
|
self.code.instructions.write_adaptive_counter(
|
||||||
@@ -8620,8 +8599,8 @@ impl ExecutingFrame<'_> {
|
|||||||
a.downcast_ref_if_exact::<PyInt>(vm),
|
a.downcast_ref_if_exact::<PyInt>(vm),
|
||||||
b.downcast_ref_if_exact::<PyInt>(vm),
|
b.downcast_ref_if_exact::<PyInt>(vm),
|
||||||
) {
|
) {
|
||||||
if specialization_compact_int_value(a_int, vm).is_some()
|
if cpython_compact_int_value(a_int, vm).is_some()
|
||||||
&& specialization_compact_int_value(b_int, vm).is_some()
|
&& cpython_compact_int_value(b_int, vm).is_some()
|
||||||
{
|
{
|
||||||
Some(Instruction::CompareOpInt)
|
Some(Instruction::CompareOpInt)
|
||||||
} else {
|
} else {
|
||||||
@@ -8684,10 +8663,7 @@ impl ExecutingFrame<'_> {
|
|||||||
&& cls.slots.as_sequence.length.load().is_none()
|
&& cls.slots.as_sequence.length.load().is_none()
|
||||||
{
|
{
|
||||||
// Cache type version for ToBoolAlwaysTrue guard
|
// Cache type version for ToBoolAlwaysTrue guard
|
||||||
let mut type_version = cls.tp_version_tag.load(Acquire);
|
let type_version = cls.version_for_specialization(vm);
|
||||||
if type_version == 0 {
|
|
||||||
type_version = cls.assign_version_tag();
|
|
||||||
}
|
|
||||||
if type_version != 0 {
|
if type_version != 0 {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.code
|
self.code
|
||||||
@@ -8919,7 +8895,7 @@ impl ExecutingFrame<'_> {
|
|||||||
idx.downcast_ref_if_exact::<PyInt>(vm),
|
idx.downcast_ref_if_exact::<PyInt>(vm),
|
||||||
) {
|
) {
|
||||||
let list_len = list.borrow_vec().len();
|
let list_len = list.borrow_vec().len();
|
||||||
if specialization_nonnegative_compact_index(int_idx, vm).is_some_and(|i| i < list_len) {
|
if cpython_nonnegative_compact_index(int_idx, vm).is_some_and(|i| i < list_len) {
|
||||||
Some(Instruction::StoreSubscrListInt)
|
Some(Instruction::StoreSubscrListInt)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -9025,10 +9001,7 @@ impl ExecutingFrame<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get or assign type version
|
// Get or assign type version
|
||||||
let mut type_version = cls.tp_version_tag.load(Acquire);
|
let type_version = cls.version_for_specialization(vm);
|
||||||
if type_version == 0 {
|
|
||||||
type_version = cls.assign_version_tag();
|
|
||||||
}
|
|
||||||
if type_version == 0 {
|
if type_version == 0 {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.code.instructions.write_adaptive_counter(
|
self.code.instructions.write_adaptive_counter(
|
||||||
|
|||||||
@@ -840,6 +840,7 @@ pub mod module {
|
|||||||
reinit_mutex_after_fork(&vm.state.atexit_funcs);
|
reinit_mutex_after_fork(&vm.state.atexit_funcs);
|
||||||
reinit_mutex_after_fork(&vm.state.global_trace_func);
|
reinit_mutex_after_fork(&vm.state.global_trace_func);
|
||||||
reinit_mutex_after_fork(&vm.state.global_profile_func);
|
reinit_mutex_after_fork(&vm.state.global_profile_func);
|
||||||
|
reinit_mutex_after_fork(&vm.state.type_mutex);
|
||||||
reinit_mutex_after_fork(&vm.state.monitoring);
|
reinit_mutex_after_fork(&vm.state.monitoring);
|
||||||
|
|
||||||
// PyGlobalState parking_lot::Mutex locks
|
// PyGlobalState parking_lot::Mutex locks
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ where
|
|||||||
switch_interval: AtomicCell::new(0.005),
|
switch_interval: AtomicCell::new(0.005),
|
||||||
global_trace_func: PyMutex::default(),
|
global_trace_func: PyMutex::default(),
|
||||||
global_profile_func: PyMutex::default(),
|
global_profile_func: PyMutex::default(),
|
||||||
|
type_mutex: PyMutex::default(),
|
||||||
#[cfg(feature = "threading")]
|
#[cfg(feature = "threading")]
|
||||||
main_thread_ident: AtomicCell::new(0),
|
main_thread_ident: AtomicCell::new(0),
|
||||||
#[cfg(feature = "threading")]
|
#[cfg(feature = "threading")]
|
||||||
|
|||||||
@@ -599,6 +599,8 @@ pub struct PyGlobalState {
|
|||||||
pub global_trace_func: PyMutex<Option<PyObjectRef>>,
|
pub global_trace_func: PyMutex<Option<PyObjectRef>>,
|
||||||
/// Global profile function for all threads (set by sys._setprofileallthreads)
|
/// Global profile function for all threads (set by sys._setprofileallthreads)
|
||||||
pub global_profile_func: PyMutex<Option<PyObjectRef>>,
|
pub global_profile_func: PyMutex<Option<PyObjectRef>>,
|
||||||
|
/// Global type mutation/versioning mutex for CPython-style FT type operations.
|
||||||
|
pub type_mutex: PyMutex<()>,
|
||||||
/// Main thread identifier (pthread_self on Unix)
|
/// Main thread identifier (pthread_self on Unix)
|
||||||
#[cfg(feature = "threading")]
|
#[cfg(feature = "threading")]
|
||||||
pub main_thread_ident: AtomicCell<u64>,
|
pub main_thread_ident: AtomicCell<u64>,
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ from functools import reduce
|
|||||||
from unittest.runner import registerResult, result
|
from unittest.runner import registerResult, result
|
||||||
|
|
||||||
|
|
||||||
|
def _get_method_dict(test):
|
||||||
|
"""Get the __dict__ of the underlying function for a test method.
|
||||||
|
|
||||||
|
Works for both bound methods (__func__.__dict__) and plain functions.
|
||||||
|
"""
|
||||||
|
method = getattr(test, test._testMethodName)
|
||||||
|
func = getattr(method, '__func__', method)
|
||||||
|
return func.__dict__
|
||||||
|
|
||||||
|
|
||||||
class TablePrinter(object):
|
class TablePrinter(object):
|
||||||
# Modified from https://github.com/agramian/table-printer, same license as above
|
# Modified from https://github.com/agramian/table-printer, same license as above
|
||||||
"Print a list of dicts as a table"
|
"Print a list of dicts as a table"
|
||||||
@@ -325,7 +335,7 @@ class CustomTextTestResult(result.TestResult):
|
|||||||
self.stream.writeln("TEST SUITE: %s" % self.suite)
|
self.stream.writeln("TEST SUITE: %s" % self.suite)
|
||||||
self.stream.writeln("Description: %s" % self.getSuiteDescription(test))
|
self.stream.writeln("Description: %s" % self.getSuiteDescription(test))
|
||||||
try:
|
try:
|
||||||
name_override = getattr(test, test._testMethodName).__func__.__dict__[
|
name_override = _get_method_dict(test)[
|
||||||
"test_case_name"
|
"test_case_name"
|
||||||
]
|
]
|
||||||
except:
|
except:
|
||||||
@@ -382,30 +392,30 @@ class CustomTextTestResult(result.TestResult):
|
|||||||
).__func__.__dict__ and set([s.lower() for s in self.test_types]) == set(
|
).__func__.__dict__ and set([s.lower() for s in self.test_types]) == set(
|
||||||
[
|
[
|
||||||
s.lower()
|
s.lower()
|
||||||
for s in getattr(test, test._testMethodName).__func__.__dict__[
|
for s in _get_method_dict(test)[
|
||||||
"test_type"
|
"test_type"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
getattr(test, test._testMethodName).__func__.__dict__[
|
_get_method_dict(test)[
|
||||||
"__unittest_skip_why__"
|
"__unittest_skip_why__"
|
||||||
] = 'Test run specified to only run tests of type "%s"' % ",".join(
|
] = 'Test run specified to only run tests of type "%s"' % ",".join(
|
||||||
self.test_types
|
self.test_types
|
||||||
)
|
)
|
||||||
getattr(test, test._testMethodName).__func__.__dict__[
|
_get_method_dict(test)[
|
||||||
"__unittest_skip__"
|
"__unittest_skip__"
|
||||||
] = True
|
] = True
|
||||||
if "skip_device" in getattr(test, test._testMethodName).__func__.__dict__:
|
if "skip_device" in _get_method_dict(test):
|
||||||
for device in getattr(test, test._testMethodName).__func__.__dict__[
|
for device in _get_method_dict(test)[
|
||||||
"skip_device"
|
"skip_device"
|
||||||
]:
|
]:
|
||||||
if self.config and device.lower() in self.config["device_name"].lower():
|
if self.config and device.lower() in self.config["device_name"].lower():
|
||||||
getattr(test, test._testMethodName).__func__.__dict__[
|
_get_method_dict(test)[
|
||||||
"__unittest_skip_why__"
|
"__unittest_skip_why__"
|
||||||
] = "Test is marked to be skipped on %s" % device
|
] = "Test is marked to be skipped on %s" % device
|
||||||
getattr(test, test._testMethodName).__func__.__dict__[
|
_get_method_dict(test)[
|
||||||
"__unittest_skip__"
|
"__unittest_skip__"
|
||||||
] = True
|
] = True
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user