Skip to content

Commit 653b6c5

Browse files
committed
sysconfig, sizeof: advertise a build without the lock, and report its layout
#1236 moved `Py_GIL_DISABLED` to 0 and dropped the `t` from `sys.abiflags`, `ABIFLAGS`, `sys.winver` and the installed stdlib directory. Restore the four spellings and `lib/pyre3.14t`, including the cpyext include directory, `stage-stdlib.py`, the `importing.rs` search paths and the `dist-workspace.toml` comment naming the archived directory. The blackhole and residual-call halves of that commit are untouched. The object header such a build carries is four words, not two, and the `tp_basicsize`/`tp_itemsize` projection reported the two-word layout: - `cpython_type_layout`: the entries that grow by two words, plus `type` and `PyWeakReference`, which grow by three. - `cpython_type_offsets`: the `type`, `set`/`frozenset` and `memoryview` inline offsets, and the managed weakref word, which sits two words behind the instance rather than four. - `str.__sizeof__`: PyASCIIObject 7 words, PyCompactUnicodeObject 9, PyUnicodeObject 10. - `type.__sizeof__`: PyHeapTypeObject 120 words, PyTypeObject 54. - `object.__sizeof__`: the fallback for a type with no entry. - `sys.getsizeof`: `_PyType_PreHeaderSize` charges no `PyGC_Head`. Without the lock the collector keeps its bits in the object header, so a tracked instance pays nothing for the wider header while an untracked one pays the full two words. - `list.__sizeof__`: `list_sort_impl` writes -1 into `allocated` while the items are detached; clamp it rather than wrap in `size_t`. `cpython_object_is_gc` loses its only caller and keeps the flag it reads; the comments on it and on `flag_have_gc` no longer claim `getsizeof` adds a collector pre-header. `test.test_interpreters` is already recorded SKIP in `KNOWN_SKIPS` and the baseline by #1253, whose stated reason — the package `__init__` raises `SkipTest("GIL disabled")` when `Py_GIL_DISABLED` — holds again with the advertisement restored. Values measured against CPython 3.14.6 free-threaded: 22 of the 23 types the table covers now answer the same number. `list_reverseiterator` still has no entry and reports 32 against 48; that gap predates this change. `test.test_str` `test_raiseMemError` was the visible failure; the rest of the projection is reached only by `@support.cpython_only` assertions, which pyre skips. Assisted-by: Claude
1 parent bc56455 commit 653b6c5

9 files changed

Lines changed: 126 additions & 109 deletions

File tree

‎dist-workspace.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ install-updater = false
2929
# and it is included beside the executable in every archive and installer.
3030
# `include` keeps each item's own name and has no per-target form, so the one
3131
# entry names the stdlib directory on every platform: `lib` holding
32-
# `pyre3.14`, except on Windows where the stdlib is `Lib` itself and the two
32+
# `pyre3.14t`, except on Windows where the stdlib is `Lib` itself and the two
3333
# spellings are the same path.
3434
include = ["dist-assets/lib"]
3535
github-build-setup = "../pyre-dist-build-setup.yml"
File renamed without changes.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8420,7 +8420,7 @@ result = (
84208420
24, 24, 24, 24]
84218421
and [allocation(item) for item in sources] == [6, 6, 6, 8, 8, 8, 8, 8, 8]
84228422
and allocation(list(HintTwenty())) == 8
8423-
and sort_seen == [(0, 32), (0, 32), (0, 32)]
8423+
and sort_seen == [(0, 56), (0, 56), (0, 56)]
84248424
and sorted_value == [1, 2, 3]
84258425
and allocation(sorted_value) == 4
84268426
and noop_sorted == [1, 2, 3]

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

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -849,9 +849,8 @@ fn init_string_module(ns: PyObjectRef) {
849849
/// `sysconfig._init_non_posix` SUBSCRIPTS `Py_GIL_DISABLED` and `Py_DEBUG` to
850850
/// spell `ABIFLAGS`, so on Windows a missing key is a `KeyError` out of the
851851
/// first `get_config_var` call rather than the `None` the `.get()` readers
852-
/// take. Pyre runs its mutators under a global interpreter lock
853-
/// (`majit-gc/src/rgil.rs`, the `thread_gil.c` port), so `Py_GIL_DISABLED` is
854-
/// 0 and the derived `ABIFLAGS` is empty; `Py_DEBUG` is 0.
852+
/// take. Pyre runs its mutators without a global interpreter lock, so
853+
/// `Py_GIL_DISABLED` is 1 and the derived `ABIFLAGS` is `t`; `Py_DEBUG` is 0.
855854
///
856855
/// `EXT_SUFFIX` and `SOABI` name pyre's own cpyext ABI, never CPython's ABI
857856
/// tag. They are build metadata rather than a claim that an extension loader
@@ -869,7 +868,7 @@ fn init_sysconfig_stub(ns: PyObjectRef) {
869868
let vars = pyre_object::w_dict_new();
870869
let so_ext = extension_abi_suffix();
871870
unsafe {
872-
for (name, value) in [("Py_DEBUG", 0), ("Py_GIL_DISABLED", 0)] {
871+
for (name, value) in [("Py_DEBUG", 0), ("Py_GIL_DISABLED", 1)] {
873872
pyre_object::w_dict_store(
874873
vars,
875874
pyre_object::w_str_new(name),
@@ -1087,14 +1086,12 @@ fn init_sysconfigdata(ns: PyObjectRef) {
10871086
));
10881087

10891088
let vars = pyre_object::w_dict_new();
1090-
// Empty, matching the `Py_GIL_DISABLED` of 0 that `_init_non_posix`
1091-
// derives the non-posix spelling from, and matching the lower-case
1092-
// `abiflags` that forms the include and site-packages directory names —
1093-
// a separate variable read from `sys.abiflags` (`sysconfig:545`). The
1094-
// release tree has no `t` suffix on its stdlib directory for
1095-
// `site.py:409` to find, and `sysconfig`'s `abi_thread` must name the
1096-
// same directory `site.py` does.
1097-
store_str(vars, "ABIFLAGS", "");
1089+
// `_init_non_posix` derives the same `t` from `Py_GIL_DISABLED` below.
1090+
// The lower-case `abiflags` that forms the include and site-packages
1091+
// directory names is a separate variable, read from `sys.abiflags`
1092+
// (`sysconfig:545`), and stays empty: the release tree has no `t` suffix
1093+
// on its stdlib directory for `site.py:409` to find.
1094+
store_str(vars, "ABIFLAGS", "t");
10981095
store_str(vars, "SOABI", &soabi);
10991096
// Deprecated in Python 3, kept for backward compatibility.
11001097
store_str(vars, "SO", &so_ext);
@@ -1114,13 +1111,11 @@ fn init_sysconfigdata(ns: PyObjectRef) {
11141111
store_str(vars, "EXE", "");
11151112
store_str(vars, "VERSION", "3.14");
11161113
store_str(vars, "LDVERSION", "3.14");
1117-
// cpyext never uses Py_DEBUG. Pyre runs its mutators under a global
1118-
// interpreter lock (`majit-gc/src/rgil.rs` ports `thread_gil.c`), so it is
1119-
// not a free-threaded build: `test.support.Py_GIL_DISABLED` reads this key
1120-
// and skips whole suites on it. Py_ENABLE_SHARED at 1 would add a python
1121-
// shared object to link lines as `-lpython3.x`.
1114+
// cpyext never uses Py_DEBUG. Pyre runs its mutators without a global
1115+
// interpreter lock. Py_ENABLE_SHARED at 1 would add a python shared
1116+
// object to link lines as `-lpython3.x`.
11221117
store_int(vars, "Py_DEBUG", 0);
1123-
store_int(vars, "Py_GIL_DISABLED", 0);
1118+
store_int(vars, "Py_GIL_DISABLED", 1);
11241119
store_int(vars, "Py_ENABLE_SHARED", 0);
11251120
// Pyre currently has neither a CPython-compatible C API nor a separately
11261121
// linkable runtime library. Keep the build ABI metadata above for wheel
@@ -1781,7 +1776,7 @@ pub(crate) struct StartupPathConfig {
17811776
pub base_prefix: PathBuf,
17821777
/// Bootstrap search entries in PyPy's order. A source checkout keeps
17831778
/// `lib_pypy` before `lib-python/3`; an installed tree has one merged
1784-
/// `lib/pyre3.14` entry.
1779+
/// `lib/pyre3.14t` entry.
17851780
pub stdlib_paths: Vec<PathBuf>,
17861781
/// The language stdlib root (the entry containing `os.py` / `site.py`).
17871782
/// This is exposed as `sys._stdlib_dir` and is deliberately distinct from
@@ -1913,7 +1908,7 @@ fn find_invoked_executable() -> PathBuf {
19131908
}
19141909

19151910
/// Recognize both PyPy-style source trees (`lib-python/3`) and the packaged
1916-
/// Pyre layout (`lib/pyre3.14`). The latter is the installation shape pip and
1911+
/// Pyre layout (`lib/pyre3.14t`). The latter is the installation shape pip and
19171912
/// venv will consume; accepting the former keeps the repository executable as
19181913
/// the untranslated/development oracle, matching PyPy's two
19191914
/// `compute_stdlib_path_*` arms.
@@ -1950,7 +1945,7 @@ fn stdlib_at_prefix(prefix: &Path) -> Option<(Vec<PathBuf>, Option<PathBuf>)> {
19501945
// `compute_stdlib_path`: release packaging merges the two source trees
19511946
// into one implementation-version directory.
19521947
for packaged in [
1953-
prefix.join("lib").join("pyre3.14"),
1948+
prefix.join("lib").join("pyre3.14t"),
19541949
// cargo-dist's Homebrew formula installs non-binary archive contents
19551950
// into `pkgshare` (`<keg>/share/pyrex`). This is still a single
19561951
// interpreter-owned prefix, analogous to PyPy's macOS bundle search
@@ -1959,7 +1954,7 @@ fn stdlib_at_prefix(prefix: &Path) -> Option<(Vec<PathBuf>, Option<PathBuf>)> {
19591954
.join("share")
19601955
.join("pyrex")
19611956
.join("lib")
1962-
.join("pyre3.14"),
1957+
.join("pyre3.14t"),
19631958
] {
19641959
if packaged.join("site.py").is_file() {
19651960
let mut paths = vec![packaged.clone()];
@@ -2081,7 +2076,7 @@ fn prefix_from_stdlib(stdlib: &Path) -> PathBuf {
20812076
.is_some_and(|name| name == "lib")
20822077
{
20832078
// cargo-dist Homebrew data lives at
2084-
// `<keg>/share/pyrex/lib/pyre3.14`, while the executable still lives
2079+
// `<keg>/share/pyrex/lib/pyre3.14t`, while the executable still lives
20852080
// at `<keg>/bin/pyre`. An explicit PYRE_STDLIB pointing at that data
20862081
// must recover the keg, not claim `<keg>/share/pyrex` as sys.prefix.
20872082
if let Some(pyre_share) = parent.and_then(Path::parent)
@@ -5299,12 +5294,12 @@ mod tests {
52995294
PathBuf::from("/src/pyre")
53005295
);
53015296
assert_eq!(
5302-
prefix_from_stdlib(Path::new("/opt/pyre/lib/pyre3.14")),
5297+
prefix_from_stdlib(Path::new("/opt/pyre/lib/pyre3.14t")),
53035298
PathBuf::from("/opt/pyre")
53045299
);
53055300
assert_eq!(
53065301
prefix_from_stdlib(Path::new(
5307-
"/opt/homebrew/Cellar/pyrex/0.0.2/share/pyrex/lib/pyre3.14"
5302+
"/opt/homebrew/Cellar/pyrex/0.0.2/share/pyrex/lib/pyre3.14t"
53085303
)),
53095304
PathBuf::from("/opt/homebrew/Cellar/pyrex/0.0.2")
53105305
);
@@ -5322,7 +5317,7 @@ mod tests {
53225317
#[test]
53235318
fn stdlib_layout_prefers_the_versioned_zip_without_claiming_a_directory() {
53245319
let tree = tempfile::tempdir().unwrap();
5325-
let packaged = tree.path().join("lib/pyre3.14");
5320+
let packaged = tree.path().join("lib/pyre3.14t");
53265321
std::fs::create_dir_all(&packaged).unwrap();
53275322
std::fs::write(packaged.join("site.py"), "").unwrap();
53285323
let zip = tree.path().join("python314.zip");
@@ -5343,7 +5338,7 @@ mod tests {
53435338
let tree = tempfile::tempdir().unwrap();
53445339
let base = tree.path().join("base");
53455340
let base_executable = base.join("bin/pyre");
5346-
let base_stdlib = base.join("lib/pyre3.14");
5341+
let base_stdlib = base.join("lib/pyre3.14t");
53475342
std::fs::create_dir_all(base_executable.parent().unwrap()).unwrap();
53485343
std::fs::create_dir_all(&base_stdlib).unwrap();
53495344
std::fs::write(&base_executable, "").unwrap();

‎pyre/pyre-interpreter/src/module/sys/vm.rs‎

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -71,24 +71,21 @@ fn get_sizeof(w_obj: PyObjectRef) -> crate::PyResult {
7171
));
7272
}
7373

74-
// `_PyType_PreHeaderSize(Py_TYPE(o))` adds its two components
75-
// independently: a two-word GC header for tracked objects, plus a two-word
76-
// managed dict/weakref prefix where the instance type requests it. A
77-
// tracked builtin such as list has only the first; a normal heap instance
78-
// has both; an untracked heap-derived value may have only the second.
74+
// `_PyType_PreHeaderSize(Py_TYPE(o))` — the two-word managed dict/weakref
75+
// prefix an instance type requests. Its other term, a `PyGC_Head` ahead of
76+
// every tracked object, is compiled out of a build without a global
77+
// interpreter lock: the collector keeps its bits in the object header
78+
// there. That is why the four-word header those builds carry costs a
79+
// tracked type nothing over the two-word one, while an untracked leaf pays
80+
// the whole two words.
7981
//
80-
// Both terms are read off the type. Asking instead which heap the instance
82+
// The term is read off the type. Asking instead which heap the instance
8183
// landed in would make the answer depend on the allocation that produced
8284
// it: a `str` folded into a code constant sits outside the collector's
8385
// ranges and one built at run time does not, so the same value reported two
8486
// different sizes.
8587
let word = std::mem::size_of::<usize>() as u64;
86-
let gc_header = if crate::typedef::cpython_object_is_gc(current()) {
87-
2 * word
88-
} else {
89-
0
90-
};
91-
let managed_prefix = crate::typedef::r#type(current()).map_or(0, |tp| unsafe {
88+
let pre_header = crate::typedef::r#type(current()).map_or(0, |tp| unsafe {
9289
if pyre_object::w_type_is_heaptype(tp.as_ptr())
9390
&& (pyre_object::w_type_get_hasdict(tp.as_ptr())
9491
|| pyre_object::w_type_get_weakrefable(tp.as_ptr()))
@@ -98,7 +95,6 @@ fn get_sizeof(w_obj: PyObjectRef) -> crate::PyResult {
9895
0
9996
}
10097
});
101-
let pre_header = gc_header + managed_prefix;
10298
let total = (size as u64)
10399
.checked_add(pre_header)
104100
.expect("Py_ssize_t plus the fixed pre-header fits in size_t");
@@ -1238,11 +1234,11 @@ pub fn register_module(ns: pyre_object::PyObjectRef) {
12381234
);
12391235
// sys.winver — the "major.minor" tag Windows uses for the per-user site
12401236
// directory and the PythonCore registry keys. site.getusersitepackages
1241-
// reads it to build USER_SITE. It carries no `t`: that suffix is how
1242-
// Windows spells what `sys.abiflags` spells elsewhere, and pyre holds a
1243-
// global interpreter lock.
1237+
// reads it to build USER_SITE. A build without a global interpreter lock
1238+
// carries the `t` here, which is how Windows spells what `sys.abiflags`
1239+
// spells elsewhere.
12441240
#[cfg(windows)]
1245-
module_ns_store(ns, "winver", w_str_new("3.14"));
1241+
module_ns_store(ns, "winver", w_str_new("3.14t"));
12461242
// sys.dllhandle — the handle of the DLL exporting the Python C API,
12471243
// published beside `winver` because both come from the same `MS_COREDLL`
12481244
// block. `ctypes/__init__.py:562` builds `pythonapi` out of it with no
@@ -2047,15 +2043,11 @@ pub fn register_module(ns: pyre_object::PyObjectRef) {
20472043
Err(unsafe { crate::PyError::from_exc_object(exc) })
20482044
}),
20492045
);
2050-
// sys.abiflags — empty, because pyre runs its mutators under a global
2051-
// interpreter lock (`majit-gc/src/rgil.rs`, the `thread_gil.c` port), so
2052-
// the `t` that spells a free-threaded ABI does not describe this build.
2053-
// `site.py:409` reads the flag to name the site-packages directory and
2054-
// `sysconfig` derives `abi_thread` from `Py_GIL_DISABLED` for the same
2055-
// name, so the two must agree. The attribute is absent on Windows, where
2056-
// the flag is spelled in `sys.winver`; every reader guards with `hasattr`,
2057-
// so an empty string answers the same.
2058-
module_ns_store(ns, "abiflags", w_str_new(""));
2046+
// sys.abiflags — `t` for a build without a global interpreter lock. The
2047+
// attribute is absent on Windows, where the flag is spelled in
2048+
// `sys.winver` and neither the `nt` scheme nor `site._get_path` reads it;
2049+
// every reader guards with `hasattr`, so an empty string answers the same.
2050+
module_ns_store(ns, "abiflags", w_str_new(if cfg!(windows) { "" } else { "t" }));
20592051
// sys.argv — pick up pending argv from set_sys_argv if available.
20602052
let pending = crate::importing::take_pending_sys_argv();
20612053
let argv = if pending.is_null() {

0 commit comments

Comments
 (0)