Skip to content

RFC: redefine PyTypeObjects as heap types for Limited API compliance#154

Merged
brandon-rhodes merged 1 commit into
brandon-rhodes:masterfrom
neutrinoceros:rfc/heaptypes
Apr 29, 2026
Merged

RFC: redefine PyTypeObjects as heap types for Limited API compliance#154
brandon-rhodes merged 1 commit into
brandon-rhodes:masterfrom
neutrinoceros:rfc/heaptypes

Conversation

@neutrinoceros

@neutrinoceros neutrinoceros commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

based off #153
needed for #151

At the time of opening, I'm getting a single compilation error:

clang++: warning: optimization flag '-ffloat-store' is not supported [-Wignored-optimization-argument]
extension/wrapper.cpp:582:10: error: use of undeclared identifier 'Py_tp_as_sequence'
  582 |         {Py_tp_as_sequence, &SatrecArray_as_sequence},

I don't have experience with "sub slots", which seem needed for defining the tp_as_sequence slot correctly. Hopefully I can crack it soon but in the mean time, any help would be appreciated.

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

maybe the rabbit hole goes a lot deeper. I think this is just the first symptom of a much broader issue: SatrecArrayType is the first type of variable lenght that I attempt to convert to a heap type. I'm almost sure it should be possible, but I may be slow to reach a conclusion/solution.

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

FWIW I doubt that this change can be handled simply without dropping Python 2.7 too, but I also don't have a simple way to check.

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

... which might not be a problem since, IIUC, the extension is only ever compiled for Python 3 anyway

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

I got it to compile locally. Hopefully it runs smoothly in CI too.

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

failure is real. I'll probably need to iterate a bit if I cannot reproduce this locally (which remains to be seen)

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

I can reproduce, this shouldn't take long

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

shotgun debugging: it seems that this is the block that errors out within the Limited API build

        if (!PyObject_IsInstance(item, (PyObject*) &SatrecType)) {
            PyErr_Format(PyExc_ValueError, "every item must be a Satrec,"
                         " but element %d is: %R", i, item);
            Py_DECREF(item);
            return -1;
        }

from
SatrecArray_init(SatrecArrayObject *self, PyObject *args, PyObject *kwds)

Comment thread extension/wrapper.cpp
@neutrinoceros neutrinoceros marked this pull request as ready for review April 20, 2026 08:27
@neutrinoceros neutrinoceros marked this pull request as draft April 20, 2026 08:37
@neutrinoceros neutrinoceros marked this pull request as ready for review April 20, 2026 08:48

@brandon-rhodes brandon-rhodes left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks again for all of these PRs, which I'll try to make better progress through in May than I did in April. Here are a few questions about this one. Once they're answered, I think it will be ready to merge! This was interesting to read, as it's my first introduction to the new mechanisms for defining types.

Comment thread extension/wrapper.cpp
Comment thread extension/wrapper.cpp
Comment thread extension/wrapper.cpp Outdated
Py_INCREF(&SatrecType);
if (PyModule_AddObject(m, "Satrec", (PyObject *) &SatrecType) < 0) {
if (PyModule_AddObject(m, "Satrec", SatrecType) < 0) {
Py_DECREF(&SatrecType);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I think you also need a Py_DECREF(&SatrecArrayType); here, since otherwise you will leak that object by losing the only pointer to it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think that's correct. I also conclude that I can collapse two now-identical blocks.

Comment thread setup.py Outdated
extra_compile_args=['-ffloat-store'],
extra_compile_args=[
'-ffloat-store',
"-std=c++20" if get_default_compiler() != "msvc" else "/std:c++20",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Let's add a comment explaining why this is necessary, and why both compilers wouldn't choose the most recent C++ automatically.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure. I'll probably need to revert this part just refresh the logs, because I don't recall the specifics now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

see this log: https://github.com/brandon-rhodes/python-sgp4/actions/runs/25109895590/job/73581108136?pr=154

  extension/wrapper.cpp(22): warning C4200: nonstandard extension used: zero-sized array in struct/union
  extension/wrapper.cpp(22): note: This member will be ignored by a defaulted constructor or copy/move assignment operator
  extension/wrapper.cpp(202): warning C4244: 'initializing': conversion from 'long' to 'char', possible loss of data
  extension/wrapper.cpp(565): error C7555: use of designated initializers requires at least '/std:c++20'
  extension/wrapper.cpp(583): error C7555: use of designated initializers requires at least '/std:c++20'
  error: command 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC\\14.44.35207\\bin\\HostX86\\x86\\cl.exe' failed with exit code 2

I don't know why msvc doesn't pick c++20 on its own though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

(I added this line back on my latest push so that you can merge this if you want)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

  1. If -std=c++20 doesn't seem to be needed, then it would be nice to adjust this so that it's not supplied; I'd rather not introduce arguments that aren't necessary.
  2. If you could, adjust the if to be positive rather than negative; that's a Raymond Hettinger piece of advice (if I recall?) that does tend to make things more readable for me in the future.
  3. Thanks for digging out those logs! I can add a comment after merging to explain to myself why we have to adjust the MS compile argument.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah, a fellow Hettinbro !
hat tip

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

Answering from my phone right now. 3/4 of your questions require my double-checking. I'll try to squeeze this into my afternoon work session !

@neutrinoceros neutrinoceros marked this pull request as draft April 29, 2026 12:51
@neutrinoceros neutrinoceros marked this pull request as ready for review April 29, 2026 13:48
Comment thread setup.py Outdated
extra_compile_args=['-ffloat-store'],
extra_compile_args=[
'-ffloat-store',
"-std=c++20" if get_default_compiler() != "msvc" else "/std:c++20",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

  1. If -std=c++20 doesn't seem to be needed, then it would be nice to adjust this so that it's not supplied; I'd rather not introduce arguments that aren't necessary.
  2. If you could, adjust the if to be positive rather than negative; that's a Raymond Hettinger piece of advice (if I recall?) that does tend to make things more readable for me in the future.
  3. Thanks for digging out those logs! I can add a comment after merging to explain to myself why we have to adjust the MS compile argument.

Comment thread extension/wrapper.cpp Outdated
Py_INCREF(&SatrecArrayType);
if (PyModule_AddObject(m, "SatrecArray", (PyObject *) &SatrecArrayType) < 0) {
if ((PyModule_AddObject(m, "Satrec", SatrecType) < 0)
|| (PyModule_AddObject(m, "SatrecArray", SatrecArrayType) < 0)) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Alas, I think this introduces an error in the case that the first PyModule_AddObject() succeeds but the second fails: in that case, we wind up calling Py_DECREF(&SatrecType); with the result that SatrecType is freed, even though it's still a member of the module with the name "Satrec". When the module is the finalized and discarded, I think it would experience a double-free?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah, that makes sense. Sorry I didn't think of that.

Comment thread extension/wrapper.cpp Outdated
if ((PyModule_AddObject(m, "Satrec", SatrecType) < 0)
|| (PyModule_AddObject(m, "SatrecArray", SatrecArrayType) < 0)) {
Py_DECREF(&SatrecArrayType);
Py_DECREF(&SatrecType);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

As I re-read all these decref's, I'm confused about why they say things like Py_DECREF(&SatrecType); rather than Py_DECREF(SatrecType);. Isn't SatrecType already a pointer? With the & added, it looks like it would be trying to free the pointer rather than the object?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

you're right. I don't know where this mistake came from. I probably was thinking compilation would just fail if I passed the wrong type. I'm actually not sure why it didn't. How is PyObject** acceptable where a PyObject* is expected ? (curious to know if you got an explanation for this, but I'll fix this in any case)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I was asking myself exactly the same question as I was reading it! 🙂 I don't know why the compiler didn't flag it.

@brandon-rhodes

Copy link
Copy Markdown
Owner

Thanks so much for the forward progress on this! I've made three final comments which I think should resolve the remaining snags that I might see in the code.

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

Great, thank you so much for you careful review. I think all problems you spotted should be addressed now !

@brandon-rhodes brandon-rhodes merged commit 4db47a0 into brandon-rhodes:master Apr 29, 2026
21 checks passed
@neutrinoceros neutrinoceros deleted the rfc/heaptypes branch April 29, 2026 20:38
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.

2 participants