Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PEP 670 amendments #2157

Merged
merged 4 commits into from
Nov 24, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 67 additions & 27 deletions pep-0670.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ The `GCC documentation
<https://gcc.gnu.org/onlinedocs/cpp/Macro-Pitfalls.html>`_ lists several
common macro pitfalls:

- Misnesting;
- Operator precedence problems;
- Swallowing the semicolon;
- Duplication of side effects;
- Self-referential macros;
- Argument prescan;
- Newlines in arguments.
- Misnesting
- Operator precedence problems
- Swallowing the semicolon
- Duplication of side effects
- Self-referential macros
- Argument prescan
- Newlines in arguments


Performance and inlining
Expand All @@ -91,7 +91,7 @@ performances and reliable benchmarks. PGO helps the compiler to decide
if function should be inlined or not.

``./configure --with-pydebug`` uses the ``-Og`` compiler option if it's
supported by the compiler (GCC and LLVM clang support it): optimize
supported by the compiler (GCC and LLVM Clang support it): optimize
debugging experience. Otherwise, the ``-O0`` compiler option is used:
disable most optimizations.

Expand Down Expand Up @@ -119,11 +119,10 @@ The ``Py_ALWAYS_INLINE`` macro can be used to force inlining. This macro
uses ``__attribute__((always_inline))`` with GCC and Clang, and
``__forceinline`` with MSC.

So far, previous attempts to use ``Py_ALWAYS_INLINE`` didn't show any
benefit and were abandoned. See for example: `bpo-45094
<https://bugs.python.org/issue45094>`_: "Consider using
``__forceinline`` and ``__attribute__((always_inline))`` on static
inline functions (``Py_INCREF``, ``Py_TYPE``) for debug build".
Previous attempts to use ``Py_ALWAYS_INLINE`` didn't show any benefit, and were
abandoned. See for example: `bpo-45094 <https://bugs.python.org/issue45094>`_:
"Consider using ``__forceinline`` and ``__attribute__((always_inline))`` on
static inline functions (``Py_INCREF``, ``Py_TYPE``) for debug build".

When the ``Py_INCREF()`` macro was converted to a static inline
functions in 2018 (`commit
Expand All @@ -140,8 +139,8 @@ Disable inlining
----------------

On the other side, the ``Py_NO_INLINE`` macro can be used to disable
inlining. It is useful to reduce the stack memory usage. It is
especially useful on a LTO+PGO build which is more aggressive to inline
inlining. It can be used to reduce the stack memory usage, or to prevent
inlining on LTO+PGO builds, which are generally more aggressive to inline
code: see `bpo-33720 <https://bugs.python.org/issue33720>`_. The
``Py_NO_INLINE`` macro uses ``__attribute__ ((noinline))`` with GCC and
Clang, and ``__declspec(noinline)`` with MSC.
Expand All @@ -164,6 +163,10 @@ The following macros should not be converted:
* Compatibility layer for different C compilers, C language extensions,
or recent C features.
Example: ``#define Py_ALWAYS_INLINE __attribute__((always_inline))``.
* Macros that need the stringification features of the C preprocessor. Such
macros should be matched with an ``#undef`` after use, if possible.
* Macros that need the concatenation features of the C preprocessor. Such
macros should be matched with an ``#undef`` after use, if possible.


Convert static inline functions to regular functions
Expand All @@ -183,13 +186,16 @@ Using static inline functions in the internal C API is fine: the
internal C API exposes implementation details by design and should not be
used outside Python.

The performance impact must be measured and regarded before converting a macro
to a static inline function or a regular function.

Cast to PyObject*
-----------------

When a macro is converted to a function and the macro casts its
arguments to ``PyObject*``, the new function comes with a new macro
which cast arguments to ``PyObject*`` to prevent emitting new compiler
warnings. So the converted functions still accept pointers to other
warnings. This implies that a converted function will accept pointers to
structures inheriting from ``PyObject`` (ex: ``PyTupleObject``).

For example, the ``Py_TYPE(obj)`` macro casts its ``obj`` argument to
Expand All @@ -212,14 +218,10 @@ Remove the return value
-----------------------

When a macro is implemented as an expression, it has an implicit return
value. In some cases, the macro must not have a return value and can be
misused in third party C extensions. See `bpo-30459
<https://bugs.python.org/issue30459>`_ for the example of
``PyList_SET_ITEM()`` and ``PyCell_SET()`` macros. It is not easy to
notice this issue while reviewing macro code.

These macros are converted to functions using the ``void`` return type
to remove their return value. Removing the return value aids detecting
value. This macro pitfall can be misused in third party C extensions. See
`bpo-30459 <https://bugs.python.org/issue30459>`_ regarding the misuse of the
``PyList_SET_ITEM()`` and ``PyCell_SET()`` macros. Such pitfalls are hard to
catch while reviewing macro code. Removing the return value aids detecting
bugs in C extensions when the C API is misused.


Expand Down Expand Up @@ -249,9 +251,9 @@ the macro.
People using macros should be considered "consenting adults". People who
feel unsafe with macros should simply not use them.

The idea was rejected because macros are error prone and it is too easy
to miss a macro pitfall when writing a macro. Moreover, macros are
harder to read and to maintain than functions.
The ideas are rejected because macros _are_ error prone, and it is too easy
to miss a macro pitfall when writing and reviewing macro code. Moreover, macros
are harder to read and maintain than functions.


Examples of hard to read macros
Expand Down Expand Up @@ -318,6 +320,44 @@ Python 3.8 function (simplified code)::
}


PyUnicode_READ_CHAR()
---------------------

This macro reuses arguments, and possibly calls ``PyUnicode_KIND`` multiple
times::

#define PyUnicode_READ_CHAR(unicode, index) \
(assert(PyUnicode_Check(unicode)), \
assert(PyUnicode_IS_READY(unicode)), \
(Py_UCS4) \
(PyUnicode_KIND((unicode)) == PyUnicode_1BYTE_KIND ? \
((const Py_UCS1 *)(PyUnicode_DATA((unicode))))[(index)] : \
(PyUnicode_KIND((unicode)) == PyUnicode_2BYTE_KIND ? \
((const Py_UCS2 *)(PyUnicode_DATA((unicode))))[(index)] : \
((const Py_UCS4 *)(PyUnicode_DATA((unicode))))[(index)] \
) \
))

Possible implementation as a static inlined function::

static inline Py_UCS4
PyUnicode_READ_CHAR(PyObject *unicode, Py_ssize_t index)
{
assert(PyUnicode_Check(unicode));
assert(PyUnicode_IS_READY(unicode));

switch (PyUnicode_KIND(unicode)) {
case PyUnicode_1BYTE_KIND:
return (Py_UCS4)((const Py_UCS1 *)(PyUnicode_DATA(unicode)))[index];
case PyUnicode_2BYTE_KIND:
return (Py_UCS4)((const Py_UCS2 *)(PyUnicode_DATA(unicode)))[index];
case PyUnicode_4BYTE_KIND:
default:
return (Py_UCS4)((const Py_UCS4 *)(PyUnicode_DATA(unicode)))[index];
}
}


Macros converted to functions since Python 3.8
==============================================

Expand Down