From ecbcb57b6fc46dba8ed99deee6950a92ec105391 Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Fri, 23 May 2025 13:36:26 -0400 Subject: [PATCH] Disable unsafe identical code folding in BOLT astral-sh/uv#13610 reported a misbehavior that is the result of a subclass of str incorrectly having its ->tp_as_number->nb_add slot filled in with the value of PyUnicode_Type->tp_as_sequence->sq_concat. There are some times when this is an appropriate thing to do iwhen subclassing, but this is not one of them. The logic to prevent it in this case relies on two helper functions in the file, wrap_binaryfunc and wrap_binaryfunc_l, having different addresses, even though they contain identical code. For some reason BOLT does not do this optimization in the shared library (even though those are static functions and not exported), so we only started seeing this in the static build. --- cpython-unix/build-cpython.sh | 6 ++ .../patch-configure-bolt-icf-safe.patch | 84 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 cpython-unix/patch-configure-bolt-icf-safe.patch diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index 73fb8c45..f9a4ca0f 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -271,6 +271,12 @@ if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_12}" ]; then # https://github.com/python/cpython/issues/128514 patch -p1 -i ${ROOT}/patch-configure-bolt-apply-flags-128514.patch + # Disable unsafe identical code folding. Objects/typeobject.c + # update_one_slot requires that wrap_binaryfunc != wrap_binaryfunc_l, + # despite the functions being identical. + # https://github.com/python/cpython/pull/134642 + patch -p1 -i ${ROOT}/patch-configure-bolt-icf-safe.patch + # Tweak --skip-funcs to work with our toolchain. patch -p1 -i ${ROOT}/patch-configure-bolt-skip-funcs.patch fi diff --git a/cpython-unix/patch-configure-bolt-icf-safe.patch b/cpython-unix/patch-configure-bolt-icf-safe.patch new file mode 100644 index 00000000..1cc41adc --- /dev/null +++ b/cpython-unix/patch-configure-bolt-icf-safe.patch @@ -0,0 +1,84 @@ +From 91fc5ae4a5a66a03931f8cd383abd2aa062bb0e9 Mon Sep 17 00:00:00 2001 +From: Geoffrey Thomas +Date: Sat, 24 May 2025 19:04:09 -0400 +Subject: [PATCH 1/1] Use only safe identical code folding with BOLT + +"Identical code folding" (ICF) is the feature of an optimizer to find that two +functions have the same code and that they can therefore be deduplicated +in the binary. While this is usually safe, it can cause observable +behavior differences if the program relies on the fact that the two +functions have different addresses. + +CPython relies on this in (at least) Objects/typeobject.c, which defines +two functions wrap_binaryfunc() and wrap_binaryfunc_l() with the same +implementation, and stores their addresses in the slotdefs array. If +these two functions have the same address, update_one_slot() in that +file will fill in slots it shouldn't, causing, for instances, +classes defined in Python that inherit from some built-in types to +misbehave. + +As of LLVM 20 (llvm/llvm-project#116275), BOLT has a "safe ICF" mode, +where it looks to see if there are any uses of a function symbol outside +function calls (e.g., relocations in data sections) and skips ICF on +such functions. The intent is that this avoids observable behavior +differences but still saves storage as much as possible. + +This version is about two months old at the time of writing. To support +older LLVM versions, we have to turn off ICF entirely. + +This problem was previously noticed for Windows/MSVC in #53093 (and +again in #24098), where the default behavior of PGO is to enable ICF +(which they expand to "identical COMDAT folding") and we had to turn it +off. +--- + configure | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- + configure.ac | 25 ++++++++++++++++++++++++- + 2 files changed, 73 insertions(+), 2 deletions(-) + +diff --git a/configure.ac b/configure.ac +index 8d939f07505..25737e3f9d6 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -2129,6 +2129,29 @@ if test "$Py_BOLT" = 'true' ; then + else + AC_MSG_ERROR([merge-fdata is required for a --enable-bolt build but could not be found.]) + fi ++ ++ py_bolt_icf_flag="-icf=safe" ++ AC_CACHE_CHECK( ++ [whether ${LLVM_BOLT} supports safe identical code folding], ++ [py_cv_bolt_icf_safe], ++ [ ++ saved_cflags="$CFLAGS" ++ saved_ldflags="$LDFLAGS" ++ CFLAGS="$CFLAGS_NODIST" ++ LDFLAGS="$LDFLAGS_NODIST" ++ AC_LINK_IFELSE( ++ [AC_LANG_PROGRAM([[]], [[]])], ++ [py_cv_bolt_icf_safe=no ++ ${LLVM_BOLT} -icf=safe -o conftest.bolt conftest$EXEEXT >&AS_MESSAGE_LOG_FD 2>&1 dnl ++ && py_cv_bolt_icf_safe=yes], ++ [AC_MSG_FAILURE([could not compile empty test program])]) ++ CFLAGS="$saved_cflags" ++ LDFLAGS="$saved_ldflags" ++ ] ++ ) ++ if test "$py_cv_bolt_icf_safe" = no; then ++ py_bolt_icf_flag="" ++ fi + fi + + dnl Enable BOLT of libpython if built. +@@ -2184,7 +2207,7 @@ then + -reorder-blocks=ext-tsp + -reorder-functions=cdsort + -split-functions + -split-strategy=cdsplit +- -icf=1 ++ ${py_bolt_icf_flag} + -inline-all + -split-eh + -reorder-functions-use-hot-size +-- +2.39.5 (Apple Git-154) +