Skip to content

Commit 8301fa3

Browse files
committed
typedef: stamp the metatype on builtin type objects built outside the registry
`w_type_new_builtin` allocates with `w_class` null, and the sweep at the end of `init_typeobjects` fills the slot only for the types held in `TYPEOBJECT_CACHE`. Four builder families construct builtin type objects that never enter that registry — `getset_descriptor_type()`, `make_exc_class`, `posix.DirEntry`, and the `py_class_typed!` / `#[pyre_class]` natives — so those type objects kept a null `w_class`. `typedef::r#type` falls back to `gettypefor(ob_type)`, so the null is not visible from Python. Route the three `w_type_new_builtin` call sites through `new_builtin_typeobject`, which stamps `w_class = w_type()`, and chain `GETSET_DESCRIPTOR_TYPE` into the sweep for the one type built before the `type` typeobject is published. A hot `Cls.__name__` loop over 37 types read 10 of them at bridges_compiled=7 / guard_failures=1480 where the registry-resident ones read 0 / 1; after the change all 37 read 0 / 1. `synth/pypy_type_surface` moves from bridges_compiled=102 guard_failures=20498 to bridges_compiled=5 guard_failures=1012 against a recorded 5 / 1011, and `pyre/check.py` reports dynasm 405/405, cranelift 405/405, wasm 401/401. Assisted-by: Claude
1 parent 59d147b commit 8301fa3

1 file changed

Lines changed: 37 additions & 4 deletions

File tree

‎pyre/pyre-interpreter/src/typedef.rs‎

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,7 +1543,16 @@ pub fn init_typeobjects() {
15431543
.get()
15441544
.map(|v| *v as PyObjectRef)
15451545
.unwrap_or(PY_NULL);
1546-
for &w_typeobject_addr in reg.values() {
1546+
// `new_builtin_typeobject` stamps every type built once this loop has
1547+
// published `type`. The ones built before that read `PY_NULL` there
1548+
// and are filled here: the registry's own entries, plus
1549+
// `getset_descriptor`, whose factory the loop uses to build the other
1550+
// typedefs' descriptors and which therefore never enters `reg`.
1551+
for w_typeobject_addr in reg
1552+
.values()
1553+
.copied()
1554+
.chain(GETSET_DESCRIPTOR_TYPE.get().copied())
1555+
{
15471556
let w_typeobj = w_typeobject_addr as PyObjectRef;
15481557
unsafe {
15491558
if (*w_typeobj).w_class.is_null() {
@@ -2219,6 +2228,30 @@ pub(crate) unsafe fn stamp_new_descr_self(ns: PyObjectRef, type_obj: PyObjectRef
22192228
}
22202229
}
22212230

2231+
/// Build a builtin type object with its metatype stamped.
2232+
///
2233+
/// `baseobjspace.py getclass()` — a type object's class is its metatype, so
2234+
/// every builtin type object carries `w_class = type`. The `TYPEOBJECT_CACHE`
2235+
/// sweep stamps the types it registers, but the lazily built ones never enter
2236+
/// that registry: `getset_descriptor` (built inside the init loop as a
2237+
/// descriptor factory for the other typedefs), every exception class,
2238+
/// `posix.DirEntry`, and the `py_class_typed!` / `#[pyre_class]` natives.
2239+
/// Stamping at the single construction point covers all of them.
2240+
///
2241+
/// The roots built before the `type` typeobject is published read `PY_NULL`
2242+
/// here; the sweep still fills them. A type whose metatype is not `type`
2243+
/// (`_ctypes`' metaclasses) overwrites the slot after construction.
2244+
fn new_builtin_typeobject(
2245+
name: &str,
2246+
bases: PyObjectRef,
2247+
dict_ptr: *mut u8,
2248+
layout_pytype: *const PyType,
2249+
) -> PyObjectRef {
2250+
let type_obj = w_type_new_builtin(name, bases, dict_ptr, layout_pytype);
2251+
unsafe { (*type_obj).w_class = w_type() };
2252+
type_obj
2253+
}
2254+
22222255
/// Create the root `object` type. MRO = [object].
22232256
fn new_root_typeobject(name: &str, init: fn(PyObjectRef)) -> PyObjectRef {
22242257
let _roots = pyre_object::gc_roots::push_roots();
@@ -2235,7 +2268,7 @@ fn new_root_typeobject(name: &str, init: fn(PyObjectRef)) -> PyObjectRef {
22352268
unsafe { stamp_method_owners(ns, owner) };
22362269
}
22372270
let ns = pyre_object::gc_roots::shadow_stack_get(ns_slot);
2238-
let type_obj = w_type_new_builtin(
2271+
let type_obj = new_builtin_typeobject(
22392272
name,
22402273
PY_NULL,
22412274
ns as *mut u8,
@@ -2307,7 +2340,7 @@ fn new_typeobject_with_base_and_layout(
23072340
}
23082341
let bases = w_tuple_new(vec![base]);
23092342
let ns = pyre_object::gc_roots::shadow_stack_get(ns_slot);
2310-
let type_obj = w_type_new_builtin(name, bases, ns as *mut u8, layout_pytype);
2343+
let type_obj = new_builtin_typeobject(name, bases, ns as *mut u8, layout_pytype);
23112344

23122345
// typeobject.py:1273-1280 setup_builtin_type:
23132346
// parent_layout = w_bestbase.layout
@@ -2398,7 +2431,7 @@ pub fn make_builtin_type_with_bases(
23982431
init(ns);
23992432
let bases_tuple = w_tuple_new(bases.to_vec());
24002433
let ns = pyre_object::gc_roots::shadow_stack_get(ns_slot);
2401-
let type_obj = w_type_new_builtin(name, bases_tuple, ns as *mut u8, layout_pytype);
2434+
let type_obj = new_builtin_typeobject(name, bases_tuple, ns as *mut u8, layout_pytype);
24022435

24032436
unsafe {
24042437
let parent_layout = pyre_object::w_type_get_layout_ptr(base);

0 commit comments

Comments
 (0)