Skip to content

sysconfig, sizeof: advertise a build without the lock, and report its layout - #1245

Merged
youknowone merged 1 commit into
mainfrom
str
Aug 16, 2026
Merged

sysconfig, sizeof: advertise a build without the lock, and report its layout#1245
youknowone merged 1 commit into
mainfrom
str

Conversation

@youknowone

@youknowone youknowone commented Aug 15, 2026

Copy link
Copy Markdown
Owner

#1236 moved Py_GIL_DISABLED to 0 and dropped the t from sys.abiflags,
ABIFLAGS, sys.winver and the installed stdlib directory. This restores the
four spellings and lib/pyre3.14t, and makes the object-layout projection
report the header such a build actually carries.

The two halves have to move together: site.py:409 derives the site-packages
directory name from sys.abiflags while sysconfig derives the same name from
Py_GIL_DISABLED, so changing one alone installs into a directory the other
does not put on sys.path. scripts/stage-stdlib.py writes that directory,
importing.rs finds an installed tree by it, and pyrex/tests/cpyext_smoke.rs
and the dist-workspace.toml comment name it too.

The blackhole and residual-call halves of #1236 are untouched.

The layout half

The object header a build without the lock carries is four words, not two, and
the tp_basicsize/tp_itemsize projection reported the two-word layout — a
16-byte disagreement with what the build advertises.

test.test_str::test_raiseMemError is where that surfaced. Its self-check
compares sys.getsizeof(char * 42) against a struct size that
support.calcobjsize derives from Py_GIL_DISABLED, and all four subtests came
out exactly two words short.

  • 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. This is why the tracked builtins report the same size either way and
    only the leaves move.
  • 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.

The parity fixture

pyre/extra_tests/parity_tests/dict_set_sizeof_python314.py hard-coded twelve
container sizes taken from a build with the lock. The parity runner executes
each fixture under both the CPython oracle and pyre, and those two now disagree
about the build, so one set of constants cannot satisfy both — it derives the
header width from Py_GIL_DISABLED instead, the same config var
test.support.calcobjsize keys off. Branching rather than skipping keeps the
oracle asserting its own layout.

Every value is +16 (two words) on the free-threaded side, and pyre matches the
free-threaded oracle on all twelve.

test.test_interpreters

Already recorded SKIP in KNOWN_SKIPS and the baseline by #1253. Its stated
reason — test/test_interpreters/__init__.py raises SkipTest("GIL disabled")
when Py_GIL_DISABLED — describes the pre-#1236 build and holds again here.
Nothing in this PR changes the baseline.

Verification

Values were measured, not derived: every number comes from
sys.getsizeof under a CPython 3.14.6 free-threaded build, not from arithmetic
on the GIL layout. That mattered — type and weakref.ref grow by three words,
not two, and the subclass path needed the pre-header term removed as well; both
would have been wrong under arithmetic and neither is visible to the gate,
because those assertions are @support.cpython_only.

Of the 23 types the table covers, 22 now answer the same number as the oracle.
list_reverseiterator still has no cpython_type_layout entry and reports 32
against 48; that gap predates this change and is not touched here.

check result
cargo test --all --no-default-features --features dynasm 7995 passed, 0 failed (green on all three hosts in CI)
pyre/cpython_tests/run.py (full gate, local) 217 PASS, 0 FAIL, no regressions
test.test_str 8/8 PASS
free-threaded oracle diff 22/23 exact
parity_tests/run.py --dynasm-only / --cranelift-only all parity tests pass

Also verified on Linux aarch64 in a container earlier in the branch's life.

Remaining CI red is base-owned

main is red on the same jobs with the same signatures at both 4b3f447dba3 and
5b658fd871c, and none of it is touched here:

  • CPython suite (gate)test.test_dataclasses: PASS -> CRASH SIGSEGV in
    test_classvar_module_level_import, identical on main.
  • pyre/check.py (ubuntu, windows) — dynasm synth/generator_tree_recursion
    jit-stats guard_failures 2951 -> 2955, identical on main.
  • pyre/check.py wasm/cranelift ratio gates — timing-driven; main fails a
    superset of ours (5 wasm + 1 cranelift against our 4 wasm).

authored by Claude

Summary by CodeRabbit

  • Bug Fixes
    • Corrected __sizeof__() reporting for dictionaries, sets, frozensets, lists, strings, and related built-in objects when running without the GIL.
    • Updated object and weak-reference size calculations to reflect the current interpreter memory layout.
    • Fixed memory accounting for objects with managed dictionaries or weak references.
  • Tests
    • Added platform-aware size expectations and updated sorting allocation checks to validate no-GIL behavior.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 509c5bd6-e251-4e55-b304-fb80dc99a080

📥 Commits

Reviewing files that changed from the base of the PR and between 7ee4e58 and 6852025.

📒 Files selected for processing (3)
  • pyre/extra_tests/parity_tests/dict_set_sizeof_python314.py
  • pyre/pyre-interpreter/src/eval.rs
  • pyre/pyre-interpreter/src/typedef.rs

Walkthrough

The change updates no-GIL object layout constants and documentation. It aligns sys.getsizeof with type-based pre-header sizing. It also updates list, dictionary, set, frozenset, and weak-reference size checks.

Changes

No-GIL object sizing

Layer / File(s) Summary
Update lock-free object layout model
pyre/pyre-interpreter/src/typedef.rs, pyre/pyre-object/src/typeobject.rs
Unicode, builtin, heap-type, weak-reference, and fallback sizes now reflect the four-word object header. GC documentation describes collector bits in the object header.
Apply type-based sys.getsizeof sizing
pyre/pyre-interpreter/src/module/sys/vm.rs
get_sizeof now uses the type-derived pre-header and no longer adds a separate GC header charge.
Align size calculations and parity checks
pyre/pyre-interpreter/src/typedef.rs, pyre/pyre-interpreter/src/eval.rs, pyre/extra_tests/parity_tests/dict_set_sizeof_python314.py
List sizing handles the sort sentinel without unsigned wrapping. Allocation and container size assertions use the updated layout values.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 68520

The PR restores free-threaded build naming and corrects object-size and layout reporting, with the supplied checks passing and no actionable merge-blocking risk remaining after normal review.

Possibly related PRs

Poem

A rabbit counts each header word,
And finds the size now neatly stored.
GC bits rest inside the frame,
Type-based totals match the same.
The tests hop cleanly into view.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes to free-threaded build naming and object-size layout reporting.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch str

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ee4e58d84

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let is = |candidate: *const PyType| std::ptr::eq(layout, candidate);
let (base, item) = if is(&pyre_object::INSTANCE_TYPE) {
(2 * word, 0)
(4 * word, 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Account for the five-word 32-bit free-threaded header

On wasm32, which this repository explicitly supports, CPython's in-tree test.support models the free-threaded header as PHBBInP (lib-python/3/test/support/__init__.py:937-946), totaling 20 bytes when pointers and Py_ssize_t are four bytes. This line instead reports 4 * word, or 16 bytes; the same four-word assumption is repeated throughout the changed layout table and str.__sizeof__, so __basicsize__, __sizeof__, and sys.getsizeof are at least four bytes low on 32-bit builds despite advertising Py_GIL_DISABLED=1. Use architecture-correct field-layout sizes rather than a fixed word-count bump.

AGENTS.md reference: AGENTS.md:L249-L254

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 5c04e85).
Updated: 2026-08-16T01:34:29.997Z

Files in the reviewed diff
dist-workspace.toml
pyre/extra_tests/parity_tests/dict_set_sizeof_python314.py
pyre/pyre-interpreter/include/pyre3.14t/Python.h
pyre/pyre-interpreter/src/eval.rs
pyre/pyre-interpreter/src/importing.rs
pyre/pyre-interpreter/src/module/sys/vm.rs
pyre/pyre-interpreter/src/typedef.rs
pyre/pyre-object/src/typeobject.rs
pyre/pyrex/tests/cpyext_smoke.rs
scripts/stage-stdlib.py

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

None.

3. Pre-existing mismatches (already present before this patch)

  • pyre/pyre-interpreter/src/module/sys/vm.rs:42 ↔ pypy/module/sys/vm.py:355 — Pyre implements CPython-style sys.getsizeof by calling __sizeof__; PyPy deliberately returns the supplied default or raises TypeError. This predates the patch (the changed pre-header arithmetic retains that existing departure).

4. Structural adaptations

  • pyre/pyre-interpreter/src/importing.rs:871,1094,1118 ↔ rpython/rlib/rgil.py:171 — the patch advertises Py_GIL_DISABLED=1 and ABI flag t, but Pyre still uses the RPython GIL: rgil.acquire() serializes mutators. The corresponding sys.abiflags change at pyre/pyre-interpreter/src/module/sys/vm.rs:2050, size/layout changes at pyre/pyre-interpreter/src/typedef.rs:10790, and pyre3.14t release layout therefore describe a free-threaded runtime that has not been implemented. This is a GIL/free-threading structural mismatch, not a valid CPython-spec exception; it also causes tests to skip GIL-enabled coverage based on false metadata.

  • pyre/pyre-interpreter/include/pyre3.14t/Python.h:33 ↔ pypy/module/cpyext/typeobject.py:821 — the new C header defines a three-word PyObject (24 bytes on 64-bit), while pyre/pyre-interpreter/src/typedef.rs:10859 now reports a four-word object.__basicsize__ (32 bytes) for the claimed free-threaded ABI. Thus a C extension’s sizeof(PyObject) disagrees with Python-visible layout metadata. This is a fundamental Rust/cpyext structural adaptation, but it is internally inconsistent and cannot represent the advertised ABI.

@youknowone youknowone changed the title sizeof: report the object layout of the build sys.abiflags advertises sysconfig, sizeof: advertise a build without the lock, and report its layout Aug 15, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 653b6c5721

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// object to link lines as `-lpython3.x`.
store_int(vars, "Py_DEBUG", 0);
store_int(vars, "Py_GIL_DISABLED", 0);
store_int(vars, "Py_GIL_DISABLED", 1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep Py_GIL_DISABLED false while the runtime takes the GIL

Any code branching on sysconfig.get_config_var('Py_GIL_DISABLED') now treats pyre as free-threaded, but majit/majit-gc/src/gc_sync.rs:246-271 still acquires the process-wide lock for every registered runtime thread, and sys.flags.gil remains hard-coded to 1. This enables free-threaded stdlib/test paths and ABI selection even though -X gil=0 is unavailable; retain 0 until the runtime can actually execute without that lock rather than changing the advertised build to make the size checks select another layout.

AGENTS.md reference: AGENTS.md:L231-L237

Useful? React with 👍 / 👎.

let unload_marker = out_dir.join("unloaded");
let source = root.join("pyre/pyrex/tests/fixtures/cpyext_smoke.c");
let include = root.join("pyre/pyre-interpreter/include/pyre3.14");
let include = root.join("pyre/pyre-interpreter/include/pyre3.14t");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Define Py_GIL_DISABLED in the t ABI header

When an extension is compiled against the newly selected pyre3.14t/Python.h, the header never defines Py_GIL_DISABLED, despite the interpreter reporting that config variable as 1. Generated extension code already uses this macro for its ABI sanity check (for example pypy/module/cpyext/test/builtin_min_max.c:8936-8951), so it compiles the non-free-threaded branch and then raises ImportError when loaded because runtime sysconfig claims the opposite ABI.

Useful? React with 👍 / 👎.

… 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.

`dict_set_sizeof_python314.py` derives the header width from `Py_GIL_DISABLED`
rather than hard-coding it. The parity runner executes each fixture under both
the CPython oracle and pyre, and those two now disagree about the build, so one
set of constants cannot satisfy both. `test.support.calcobjsize` keys off the
same config var.

`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
@youknowone
youknowone merged commit 4a7682e into main Aug 16, 2026
15 of 17 checks passed
@youknowone
youknowone deleted the str branch August 16, 2026 03:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant