Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
09f9961
type lock
youknowone Mar 9, 2026
2a9b982
Drop old PyObjectRef outside type lock to prevent deadlock
youknowone Mar 17, 2026
4b71002
Align type lock behavior with CPython
youknowone Apr 13, 2026
9196dd4
Add PUBLISHED flag bit to RefCount state word
youknowone Jul 5, 2026
c32cb0e
Add QSBR module for deferred memory reclamation
youknowone Jul 5, 2026
9bfa070
Defer memory free of cache-published objects via QSBR
youknowone Jul 5, 2026
59a3127
Wire QSBR checkpoints into thread lifecycle and eval breaker
youknowone Jul 5, 2026
3f93b63
Publish type-cache values to QSBR and bypass cache without a VM
youknowone Jul 5, 2026
06871c4
Use try-incref reads and QSBR-backed swaps in specialization cache
youknowone Jul 5, 2026
6eecfe8
Reset QSBR thread registry after fork in the child
youknowone Jul 5, 2026
020edd3
Add threaded stress test for type cache mutation races
youknowone Jul 5, 2026
d565da5
Restrict qsbr internals to pub(crate) and fix clippy lints
youknowone Jul 5, 2026
cbd20c0
Gate per-instruction QSBR check behind a global pending flag
youknowone Jul 5, 2026
eb633b2
Merge signal and QSBR eval-breaker flags into one atomic word
youknowone Jul 5, 2026
9baa344
Skip freelist reuse for published objects in default_dealloc
youknowone Jul 5, 2026
97dbfcc
Use itoa for PyInt::to_str_radix_10 i64 fast path
youknowone Jul 5, 2026
e4ddae6
Make object attribute dunders wrapper descriptors
youknowone Jul 5, 2026
1ac47f3
Resolve TpSetattro pair on attribute deletion, not just addition
youknowone Jul 5, 2026
8f32450
Generate Opcode::cache_entries/deopt as table lookups
youknowone Jul 5, 2026
efc62e3
Make Opcode/Instruction numeric conversions O(1)
youknowone Jul 5, 2026
7e4c765
Pop CallIsinstance arguments directly instead of collecting into Vecs
youknowone Jul 5, 2026
d9796ea
Inherit tp_new instead of always installing new_wrapper
youknowone Jul 5, 2026
dd72583
Skip locals dict allocation for the init-cleanup shim frame
youknowone Jul 5, 2026
da6633d
Guard CALL_ALLOC_AND_ENTER_INIT with cached function version
youknowone Jul 5, 2026
6fe1fed
Apply rustfmt and ruff-format fixes to files from earlier commits
youknowone Jul 5, 2026
d26a553
Add frame object freelist
youknowone Jul 5, 2026
4c3bb64
Drop frame children directly in tp_clear instead of extracting
youknowone Jul 5, 2026
c197615
Untrack objects before tp_clear and guard cross-thread f_locals
youknowone Jul 5, 2026
cff6798
Skip localsplus heap copy for uniquely referenced dying frames
youknowone Jul 5, 2026
1888e7d
Stage exact-args call arguments in stack slot buffers
youknowone Jul 5, 2026
7517364
Reduce allocations in generic call paths
youknowone Jul 6, 2026
71197eb
Skip redundant exc_info restore on frame exit
youknowone Jul 6, 2026
bc2927c
Apply rustfmt to gc_state.rs
youknowone Jul 6, 2026
de52aea
Avoid empty FuncArgs clone in PyType::call
youknowone Jul 6, 2026
e10c44d
Push freelist husks only after tp_clear and child drops
youknowone Jul 6, 2026
ebc56c9
Resolve __set__ and __delete__ together for the descr_set slot
youknowone Jul 6, 2026
e902ad1
Update type base on __bases__ assignment
youknowone Jul 6, 2026
bc2aba2
Fix rollback order, dead weakrefs, and lock-held drops in set_bases
youknowone Jul 6, 2026
b8d11f2
Reify number sub-slot wrapper once in update_one_slot
youknowone Jul 6, 2026
a7fc01a
Restrict type_cache_after_fork visibility and guard type-cache reads
youknowone Jul 6, 2026
4d7126a
Guard BinaryOpSubscrGetitem with the cached type version
youknowone Jul 6, 2026
18e8474
Call __abstractmethods__ __len__ once in object.__new__
youknowone Jul 6, 2026
6133908
Use i64 fast paths for specialized int add/sub/mul
youknowone Jul 6, 2026
1499b31
Use i64 fast paths for exact int floordiv and remainder
youknowone Jul 6, 2026
70409fa
Skip trashcan and untrack for non-GC-tracked objects in dealloc
youknowone Jul 6, 2026
1b8683b
Merge trashcan depth and defer queue into one thread-local struct
youknowone Jul 6, 2026
f85c30d
Check object layout compatibility on __bases__ and __class__ assignment
youknowone Jul 6, 2026
f7dda03
Stop the world around GC pointer-reading phases
youknowone Jul 6, 2026
bca1ad9
Add threaded GC vs executing-frame stress snippet
youknowone Jul 6, 2026
2769945
Serialize fork and GC stop-the-world requesters
youknowone Jul 6, 2026
a33d16f
Add fork under concurrent GC stop-the-world snippet
youknowone Jul 6, 2026
1b0fa99
Detach while acquiring the import lock
youknowone Jul 6, 2026
92a4d00
Add concurrent-import vs GC deadlock snippet
youknowone Jul 6, 2026
d1e5e97
Create call frames untracked and track generators explicitly
youknowone Jul 6, 2026
ee94840
Track escaped call frames lazily at datastack release
youknowone Jul 6, 2026
2982b66
Guard the tracked-frame localsplus invariant
youknowone Jul 6, 2026
1fa2d5f
Reword frame/dealloc invariant comments and drop needless borrow
youknowone Jul 6, 2026
bd4b371
Move type SetAttr slot rewrite inside the type lock and tighten cache…
youknowone Jul 6, 2026
2ae9fb8
Capture the type version before reading mutable slots in specializers
youknowone Jul 6, 2026
68e8b91
Use _get_method_dict for the test_type check in the test runner
youknowone Jul 6, 2026
ec8b128
Fix lint hooks: cspell dictionary entries, spelling fix, formatting
youknowone Jul 6, 2026
9d44aaf
Rebuild all slots for type and descendants on __bases__ reassignment
youknowone Jul 6, 2026
ab0cddb
Derive the thread-local frame stack from the frame chain
youknowone Jul 6, 2026
ae106ab
Inherit sub-slot fallback into the field being resolved
youknowone Jul 6, 2026
8f4f7a5
Gate type_cache_after_fork to its fork caller and use Self in downcast
youknowone Jul 6, 2026
5f43c79
Unmark test_attr and test_method_call_error in test_monitoring
youknowone Jul 6, 2026
a3f9243
Run tp_new specialization __init__ without a trampoline frame
youknowone Jul 6, 2026
df70284
Replace per-call thread-frame mutex with an atomic top-of-stack (unix)
youknowone Jul 6, 2026
f525b38
Skip exc_info save/restore for callees that never touch the slot
youknowone Jul 6, 2026
98351a5
Return a write-through FrameLocalsProxy from frame.f_locals
youknowone Jul 6, 2026
a9f8b61
Retain the caller frame so f_back resolves after it returns
youknowone Jul 6, 2026
f05926a
Fix debug-build native stack overflow on deep recursion
youknowone Jul 6, 2026
4a92aad
Allocate exception instance __dict__ lazily
youknowone Jul 6, 2026
072bc2a
Allocate exception __dict__ lazily in vm.new_exception
youknowone Jul 6, 2026
c172c74
Validate FrameLocalsProxy.update() arguments
youknowone Jul 6, 2026
2652f4d
Address review nits in frame and faulthandler
youknowone Jul 6, 2026
0499c0a
Use scopeguard for start_the_world in faulthandler dump_all_threads
youknowone Jul 6, 2026
811f230
Gitignore docs/superpowers
youknowone Jul 7, 2026
7982da8
Rename type_bases_slot_rebuild.py to builtin_type_bases.py
youknowone Jul 7, 2026
683f164
Apply rustfmt, ruff, and cspell lint fixes
youknowone Jul 7, 2026
ac2ce2b
Gate unix-only QSBR methods to their call sites
youknowone Jul 7, 2026
f843473
Visit the function closure tuple as a GC edge
youknowone Jul 7, 2026
7ab5f76
Apply formatting hook fix to builtin_type_bases.py
youknowone Jul 7, 2026
6626177
Unmark asyncgen finalization-by-gc tests in test_base_events
youknowone Jul 7, 2026
77e6d31
Unmark test_sni_callback_refcycle in test_ssl
youknowone Jul 7, 2026
d272003
Widen GC stop-the-world gates from unix-only to all threading builds
youknowone Jul 7, 2026
e590e3b
Detach current thread around blocking _winapi/_overlapped waits
youknowone Jul 7, 2026
cdd8908
Format WaitForMultipleObjects allow_threads closure
youknowone Jul 7, 2026
b6e8cbf
Treat concurrent sni_callback removal as no-op in invoke_sni_callback
youknowone Jul 7, 2026
5e2d095
Assert capi refcount on a fresh mortal list instead of the int type o…
youknowone Jul 7, 2026
4f8116d
Traverse and GC-track PyOSError instances
youknowone Jul 8, 2026
db737e1
Unmark test_blockingioerror in test_io
youknowone Jul 8, 2026
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
1 change: 1 addition & 0 deletions .cspell.dict/rustpython.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pystr
pystruct
pystructseq
pytype
qsbr
rustix
struc
zelf
3 changes: 3 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,11 @@
"mcache",
"oparg",
"opargs",
"pointee",
"pyc",
"reborrow",
"reborrows",
"reparenting",
"reraises",
"reraising",
"significand",
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ Lib/site-packages/*
Lib/test/data/*
!Lib/test/data/README
cpython/
.claude/scheduled_tasks.lock
.claude/scheduled_tasks.lock
docs/superpowers/
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ hmac = "0.13"
indexmap = { version = "2.14.0", features = ["std"] }
insta = "1.47"
itertools = { version = "0.15.0", default-features = false, features = ["use_alloc"] }
itoa = "1"
is-macro = "0.3.7"
js-sys = "0.3"
junction = "2.0.0"
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_asyncio/test_base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1019,7 +1019,6 @@ async def iter_one():
asyncio.create_task(iter_one())
return status

@unittest.expectedFailure # TODO: RUSTPYTHON; - GC doesn't finalize async generators
def test_asyncgen_finalization_by_gc(self):
# Async generators should be finalized when garbage collected.
self.loop._process_events = mock.Mock()
Expand All @@ -1035,7 +1034,6 @@ def test_asyncgen_finalization_by_gc(self):
test_utils.run_briefly(self.loop)
self.assertTrue(status['finalized'])

@unittest.expectedFailure # TODO: RUSTPYTHON; - GC doesn't finalize async generators
def test_asyncgen_finalization_by_gc_in_other_thread(self):
# Python issue 34769: If garbage collector runs in another
# thread, async generators will not finalize in debug
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4154,7 +4154,6 @@ class E(D):
else:
self.fail("shouldn't be able to create inheritance cycles")

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_builtin_bases(self):
# Make sure all the builtin types can have their base queried without
# segfaulting. See issue #5787.
Expand Down Expand Up @@ -4199,7 +4198,6 @@ class D(C):
else:
self.fail("best_base calculation found wanting")

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_unsubclassable_types(self):
with self.assertRaises(TypeError):
class X(type(None)):
Expand Down
10 changes: 0 additions & 10 deletions Lib/test/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,6 @@ def inner():
% (file_repr, offset + 5))

class TestFrameLocals(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_scope(self):
class A:
x = 1
Expand All @@ -333,7 +332,6 @@ def f():
self.assertEqual(locals()['y'], 2)
f()

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 1 != 2
def test_closure(self):
x = 1
y = 2
Expand All @@ -356,7 +354,6 @@ def test_closure_with_inline_comprehension(self):
lst = [locals() for k in [0]]
self.assertEqual(lst[0]['k'], 0)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 3 != 4
def test_as_dict(self):
x = 1
y = 2
Expand Down Expand Up @@ -414,7 +411,6 @@ def test_non_string_key(self):
d[1] = 2
self.assertEqual(d[1], 2)

@unittest.expectedFailure # TODO: RUSTPYTHON; UnboundLocalError: local variable 'b' referenced before assignment
def test_write_with_hidden(self):
def f():
f_locals = [sys._getframe().f_locals for b in [0]][0]
Expand All @@ -426,7 +422,6 @@ def f():
c = 0
f()

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <object object at 0xb4000072b6930480> != 'a.b.c'
def test_local_objects(self):
o = object()
k = '.'.join(['a', 'b', 'c'])
Expand Down Expand Up @@ -457,7 +452,6 @@ def test_repr(self):
frame = sys._getframe()
self.assertEqual(repr(frame.f_locals), repr(dict(frame.f_locals)))

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ValueError not raised
def test_delete(self):
x = 1
d = sys._getframe().f_locals
Expand Down Expand Up @@ -501,7 +495,6 @@ def test_sizeof(self):
proxy = sys._getframe().f_locals
support.check_sizeof(self, proxy, support.calcobjsize("P"))

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: TypeError not raised
def test_unsupport(self):
x = 1
d = sys._getframe().f_locals
Expand Down Expand Up @@ -536,7 +529,6 @@ def __eq__(self, other):

return StringSubclass('x'), ImpostorX(), 'x'

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: dict_keys(['obj', 'x']) != ['obj', 'x', 'proxy']
def test_proxy_key_stringlikes_overwrite(self):
def f(obj):
x = 1
Expand All @@ -559,7 +551,6 @@ def f(obj):
self.assertEqual(keys_snapshot, expected_keys)
self.assertEqual(proxy_snapshot, expected_dict)

@unittest.expectedFailure # TODO: RUSTPYTHON; UnboundLocalError: local variable 'b' referenced before assignment
def test_proxy_key_stringlikes_ftrst_write(self):
def f(obj):
proxy = sys._getframe().f_locals
Expand Down Expand Up @@ -587,7 +578,6 @@ class ObjectSubclass:
with self.assertRaises(TypeError):
proxy[obj] = 0

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'dict' != 'FrameLocalsProxy'
def test_constructor(self):
FrameLocalsProxy = type([sys._getframe().f_locals
for x in range(1)][0])
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,6 @@ def get_frame(index):
self.assertIn('a', frame_locals)
self.assertEqual(frame_locals['a'], 42)

@unittest.expectedFailure # TODO: RUSTPYTHON; frame locals don't survive generator deallocation
def test_frame_locals_outlive_generator(self):
frame_locals1 = None

Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,6 @@ def test_frame(self):
self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals),
'(x=11, y=14)')

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'NoneType' object has no attribute 'f_code'
def test_previous_frame(self):
args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back)
self.assertEqual(args, ['a', 'b', 'c', 'd', 'e', 'f'])
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -4473,7 +4473,6 @@ def test_io_after_close(self):
self.assertRaises(ValueError, f.writelines, [])
self.assertRaises(ValueError, next, f)

@unittest.expectedFailure # TODO: RUSTPYTHON; cyclic gc
def test_blockingioerror(self):
# Various BlockingIOError issues
class C(str):
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -1984,7 +1984,6 @@ def f():
]
return d["f"], expected

@unittest.expectedFailure # TODO: RUSTPYTHON; line number differences in multi-line super() calls
def test_method_call_error(self):
nonopt_func, nonopt_expected = self._super_method_call_error(optimized=False)
opt_func, opt_expected = self._super_method_call_error(optimized=True)
Expand Down Expand Up @@ -2022,7 +2021,6 @@ def f():
]
return d["f"], expected

@unittest.expectedFailure # TODO: RUSTPYTHON; line number differences in multi-line super() calls
def test_attr(self):
nonopt_func, nonopt_expected = self._super_attr(optimized=False)
opt_func, opt_expected = self._super_attr(optimized=True)
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3122,7 +3122,7 @@ def test_pdb_issue_gh_101673():
... a = 1
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()

>>> with PdbTestInput([ # TODO: RUSTPYTHON # doctest: +NORMALIZE_WHITESPACE +EXPECTED_FAILURE
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
... '!a = 2',
... 'll',
... 'p a',
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1526,7 +1526,6 @@ def sni_callback(sock, servername, ctx): pass
self.assertIn(libssl_error_reason, str(cm.exception))
self.assertEqual(cm.exception.errno, ssl.SSL_ERROR_SSL)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <SSLContext(protocol=17)> is not None
def test_sni_callback_refcycle(self):
# Reference cycles through the servername callback are detected
# and cleared.
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -3141,7 +3141,6 @@ def last_returns_frame4(self):
def last_returns_frame5(self):
return self.last_returns_frame4()

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 1 not greater than 5
def test_extract_stack(self):
frame = self.last_returns_frame5()
def extract(**kwargs):
Expand Down
10 changes: 7 additions & 3 deletions crates/capi/src/refcount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ pub unsafe extern "C" fn Py_REFCNT(op: *mut PyObject) -> isize {

#[cfg(test)]
mod tests {
use pyo3::ffi;
use pyo3::prelude::*;
use pyo3::types::PyInt;
use pyo3::{PyTypeInfo, ffi};
use pyo3::types::PyList;

#[test]
fn refcount() {
Python::attach(|py| unsafe {
let obj = PyInt::type_object(py);
// A freshly created, non-empty list is uniquely owned here: its
// reference count is private to this test (so parallel tests cannot
// perturb it) and it is mortal (not interned), so incref then decref
// must move the count by exactly one and back.
let obj = PyList::new(py, [1, 2, 3]).unwrap();
let ref_count = ffi::Py_REFCNT(obj.as_ptr());
let obj_clone = obj.clone();
assert_eq!(ffi::Py_REFCNT(obj.as_ptr()), ref_count + 1);
Expand Down
42 changes: 39 additions & 3 deletions crates/common/src/refcount.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use crate::atomic::{Ordering, PyAtomic, Radium};

// State layout (usize):
// [1 bit: destructed] [1 bit: reserved] [1 bit: leaked] [N bits: weak_count] [M bits: strong_count]
// [1 bit: destructed] [1 bit: published] [1 bit: leaked] [N bits: weak_count] [M bits: strong_count]
// 64-bit: N=30, M=31. 32-bit: N=14, M=15.
const FLAG_BITS: u32 = 3;
const DESTRUCTED: usize = 1 << (usize::BITS - 1);
/// Object was published to a lock-free cache; memory reclamation is
/// deferred through QSBR so concurrent try-incref readers never touch
/// freed memory. Sticky once set.
const PUBLISHED: usize = 1 << (usize::BITS - 2);
const LEAKED: usize = 1 << (usize::BITS - 3);
const TOTAL_COUNT_WIDTH: u32 = usize::BITS - FLAG_BITS;
const WEAK_WIDTH: u32 = TOTAL_COUNT_WIDTH / 2;
Expand Down Expand Up @@ -72,8 +76,8 @@ impl State {
/// Reference count using state layout with LEAKED support.
///
/// State layout (usize):
/// 64-bit: [1 bit: destructed] [1 bit: reserved] [1 bit: leaked] [30 bits: weak_count] [31 bits: strong_count]
/// 32-bit: [1 bit: destructed] [1 bit: reserved] [1 bit: leaked] [14 bits: weak_count] [15 bits: strong_count]
/// 64-bit: [1 bit: destructed] [1 bit: published] [1 bit: leaked] [30 bits: weak_count] [31 bits: strong_count]
/// 32-bit: [1 bit: destructed] [1 bit: published] [1 bit: leaked] [14 bits: weak_count] [15 bits: strong_count]
pub struct RefCount {
state: PyAtomic<usize>,
}
Expand Down Expand Up @@ -187,6 +191,17 @@ impl RefCount {
pub fn is_leaked(&self) -> bool {
State::from_raw(self.state.load(Ordering::Acquire)).leaked()
}

/// Mark the object as published to a lock-free cache (sticky).
#[inline]
pub fn mark_published(&self) {
self.state.fetch_or(PUBLISHED, Ordering::Release);
}

#[inline]
pub fn is_published(&self) -> bool {
(self.state.load(Ordering::Acquire) & PUBLISHED) != 0
}
}

// Deferred Drop Infrastructure
Expand Down Expand Up @@ -279,3 +294,24 @@ pub fn flush_deferred_drops() {
}
});
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn published_bit_survives_refcount_traffic() {
let rc = RefCount::new(); // strong = 1
assert!(!rc.is_published());
rc.mark_published();
assert!(rc.is_published());
rc.inc(); // strong = 2
assert!(rc.is_published());
assert!(!rc.dec()); // strong = 1
assert!(rc.is_published());
assert!(rc.safe_inc()); // strong = 2
assert!(!rc.dec()); // strong = 1
assert!(rc.dec()); // strong = 0 -> true
assert!(rc.is_published());
}
}
Loading
Loading