You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[C-API] Convert obvious unsafe macros to static inline functions #87668
not introducing a backward incompatible C API change by mistake
not introducing new compiler warnings.
A common source of warning is that macros have no type for their arguments or return value, whereas static inline functions are strictly types. A common pattern in the Python header file is to use _PyObject_CAST() or _PyObject_CONST_CAST(). Example:
PyUnicode_WRITE, PyUnicode_READ, and PyUnicode_READ_CHAR all reuse their arguments, and there's a lot of occurrences (58 AFAICS) where they are called with the increment operator applied to the index argument. For example:
Be careful about performance. We know for sure that macros will inline. In contrast, inline functions might or might not inline (there are rules for when it can be done). When applied across source files, inlining often only occurs with an LTO build — any other build can't inline across object files. Historically, we've never had to depend on LTO, so that would be a regression for some users. We've already have a performance bug that needed to be reverted because of too aggressive conversion of macros to inline functions.
A problem with these blanket PRs is that they second guess the original developer who may have made a conscious and informed decision to use a macro. If you really think that developer was wrong, they should be included in the conversation and given the chance to opt in or out. Otherwise, we're mechanically undoing previous intelligent efforts.
Ideally, there needs to be a more nuanced view than "macros are bad, inline functions are good".
I agree we should be careful here. There are several things to consider:
Macros transformed into function may or may not get inlined so there is a risk of affecting performance if we just transform them in bulk.
Because the macro is defined as 'static inline' in the header file, then it *can* be inlined everywhere if the compiled decides to do it. It will be one copy symbol in the text segment on every compilation unit. Notice that 'inline' has a special meaning in C99 and it doesn't only mean "inline this function". It has visibility implications (see section 6.7.4 of the C99 standard).
There is risk to introducing a backwards-incompatible ABI change by mistake as Victor mentions.
This can affect also the link patterns of extension modules because these symbols may be visible in the resulting binaries when they were macro-level-inlined before. I cannot see any problem that can happen because of this but we should carefully consider it.
In short, there is the value of converting these macros for extra type safety, but we need to at the very least be very careful and if we decide to go forward this needs as many reviews as possible so we don't miss anything. I would recommend maybe start a thread about this in python-dev.
I agree we should be careful here. There are several things to consider:
Yes, and thanks for your thorough reply and insights. I do not suggest converting macros blindly, and I have no intention of doing so. Apart from the three PyUnicode_*() cases mentioned in msg388870, I've not found any misuse of macros that reuse their arguments in the CPython code base.
Perhaps using a meta-issue is the wrong approach. I guess a PEP is too much? I'll start a thread on dpo (or on the list).
Activity
erlend-aasland commentedon Mar 15, 2021
Convert macros to static inline functions if...:
See also:
vstinner commentedon Mar 15, 2021
For me, one of the my worst issue is when a macro evalutes an argument twice.
Example:
---
Surprising output:
---
$ gcc x.c -o x; ./x x=3 y=6Expected output:
---
Fixed code using a static inline function:
---
Output:
---
$ gcc x.c -o x; ./x x=2 y=4vstinner commentedon Mar 15, 2021
I would add that we should pay attention to:
A common source of warning is that macros have no type for their arguments or return value, whereas static inline functions are strictly types. A common pattern in the Python header file is to use _PyObject_CAST() or _PyObject_CONST_CAST(). Example:
For macros of the *internal* C API, it's ok to becom more strict about types. For example, in the *public* C API, I replaced:
with:
But for the internal C API, I added:
which doesn't cast its arguments.
erlend-aasland commentedon Mar 15, 2021
Should we try to split all macros like that, if possible?
erlend-aasland commentedon Mar 16, 2021
Under Include/, including subdirectories, there are 88 macros that reuse arguments.
erlend-aasland commentedon Mar 16, 2021
PyUnicode_WRITE, PyUnicode_READ, and PyUnicode_READ_CHAR all reuse their arguments, and there's a lot of occurrences (58 AFAICS) where they are called with the increment operator applied to the index argument. For example:
Objects/unicodeobject.c: PyUnicode_WRITE(kind, data, pos++, ch);
Modules/_io/textio.c: c = PyUnicode_READ(kind, in_str, i++);
Objects/stringlib/unicode_format.h: c = PyUnicode_READ_CHAR(self->str.str, self->index++);
Would a single PR for all three of them be acceptable, Victor?
rhettinger commentedon Mar 16, 2021
Be careful about performance. We know for sure that macros will inline. In contrast, inline functions might or might not inline (there are rules for when it can be done). When applied across source files, inlining often only occurs with an LTO build — any other build can't inline across object files. Historically, we've never had to depend on LTO, so that would be a regression for some users. We've already have a performance bug that needed to be reverted because of too aggressive conversion of macros to inline functions.
A problem with these blanket PRs is that they second guess the original developer who may have made a conscious and informed decision to use a macro. If you really think that developer was wrong, they should be included in the conversation and given the chance to opt in or out. Otherwise, we're mechanically undoing previous intelligent efforts.
Ideally, there needs to be a more nuanced view than "macros are bad, inline functions are good".
pablogsal commentedon Mar 17, 2021
I agree we should be careful here. There are several things to consider:
Macros transformed into function may or may not get inlined so there is a risk of affecting performance if we just transform them in bulk.
Because the macro is defined as 'static inline' in the header file, then it *can* be inlined everywhere if the compiled decides to do it. It will be one copy symbol in the text segment on every compilation unit. Notice that 'inline' has a special meaning in C99 and it doesn't only mean "inline this function". It has visibility implications (see section 6.7.4 of the C99 standard).
There is risk to introducing a backwards-incompatible ABI change by mistake as Victor mentions.
This can affect also the link patterns of extension modules because these symbols may be visible in the resulting binaries when they were macro-level-inlined before. I cannot see any problem that can happen because of this but we should carefully consider it.
In short, there is the value of converting these macros for extra type safety, but we need to at the very least be very careful and if we decide to go forward this needs as many reviews as possible so we don't miss anything. I would recommend maybe start a thread about this in python-dev.
erlend-aasland commentedon Mar 17, 2021
Pablo:
Yes, and thanks for your thorough reply and insights. I do not suggest converting macros blindly, and I have no intention of doing so. Apart from the three PyUnicode_*() cases mentioned in msg388870, I've not found any misuse of macros that reuse their arguments in the CPython code base.
Perhaps using a meta-issue is the wrong approach. I guess a PEP is too much? I'll start a thread on dpo (or on the list).
Thanks.
erlend-aasland commentedon Mar 19, 2021
FYI, thread started on https://discuss.python.org/t/what-to-do-with-unsafe-macros/7771?u=erlendaasland
erlend-aasland commentedon Mar 20, 2021
Based on the feedback, I'm closing this. The PyUnicode_* macros, or the callers, can be fixed in a separate issue.