diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,10 @@
+[0.12.0.1] — August 2023
+
+* Bug fixes:
+  * [Work around a GHC runtime linker issue on i386/PowerPC](https://github.com/haskell/bytestring/pull/604)
+
+[0.12.0.1]: https://github.com/haskell/bytestring/compare/0.12.0.0...0.12.0.1
+
 [0.12.0.0] — July 2023
 
 * __Breaking Changes__:
@@ -30,6 +37,13 @@
 
 
 [0.12.0.0]: https://github.com/haskell/bytestring/compare/0.11.5.0...0.12.0.0
+
+[0.11.5.1] — August 2023
+
+* Bug fixes:
+  * [Work around a GHC runtime linker issue on i386/PowerPC](https://github.com/haskell/bytestring/pull/604)
+
+[0.11.5.1]: https://github.com/haskell/bytestring/compare/0.11.5.0...0.11.5.1
 
 [0.11.5.0] — July 2023
 
diff --git a/bytestring.cabal b/bytestring.cabal
--- a/bytestring.cabal
+++ b/bytestring.cabal
@@ -1,5 +1,5 @@
 Name:                bytestring
-Version:             0.12.0.0
+Version:             0.12.0.1
 Synopsis:            Fast, compact, strict and lazy byte strings with a list interface
 Description:
     An efficient compact, immutable byte string type (both strict and lazy)
diff --git a/cbits/is-valid-utf8.c b/cbits/is-valid-utf8.c
--- a/cbits/is-valid-utf8.c
+++ b/cbits/is-valid-utf8.c
@@ -50,6 +50,7 @@
 #endif
 
 #include <MachDeps.h>
+#include "Rts.h"
 
 #ifdef WORDS_BIGENDIAN
 #define to_little_endian(x) __builtin_bswap64(x)
@@ -66,6 +67,29 @@
   return r;
 }
 
+// stand-in for __builtin_ctzll, used because __builtin_ctzll can
+// cause runtime linker issues for GHC in some exotic situations (#601)
+//
+// See also these ghc issues:
+//  * https://gitlab.haskell.org/ghc/ghc/-/issues/21787
+//  * https://gitlab.haskell.org/ghc/ghc/-/issues/22011
+static inline int hs_bytestring_ctz64(const uint64_t x) {
+  // These CPP conditions are taken from ghc-prim:
+  // https://gitlab.haskell.org/ghc/ghc/-/blob/73b5c7ce33929e1f7c9283ed7c2860aa40f6d0ec/libraries/ghc-prim/cbits/ctz.c#L31-57
+  // credit to Herbert Valerio Riedel, Erik de Castro Lopo
+#if defined(__GNUC__) && (defined(i386_HOST_ARCH) || defined(powerpc_HOST_ARCH))
+  uint32_t xhi = (uint32_t)(x >> 32);
+  uint32_t xlo = (uint32_t) x;
+  return xlo ? __builtin_ctz(xlo) : 32 + __builtin_ctz(xhi);
+#elif SIZEOF_UNSIGNED_LONG == 8
+  return __builtin_ctzl(x);
+#elif SIZEOF_UNSIGNED_LONG_LONG == 8
+  return __builtin_ctzll(x);
+#else
+# error no suitable __builtin_ctz() found
+#endif
+}
+
 static inline int is_valid_utf8_fallback(uint8_t const *const src,
                                          size_t const len) {
   uint8_t const *ptr = (uint8_t const *)src;
@@ -100,16 +124,16 @@
               if (results[3] == 0) {
                 ptr += 8;
               } else {
-                ptr += (__builtin_ctzll(results[3]) / 8);
+                ptr += (hs_bytestring_ctz64(results[3]) / 8);
               }
             } else {
-              ptr += (__builtin_ctzll(results[2]) / 8);
+              ptr += (hs_bytestring_ctz64(results[2]) / 8);
             }
           } else {
-            ptr += (__builtin_ctzll(results[1]) / 8);
+            ptr += (hs_bytestring_ctz64(results[1]) / 8);
           }
         } else {
-          ptr += (__builtin_ctzll(results[0]) / 8);
+          ptr += (hs_bytestring_ctz64(results[0]) / 8);
         }
       }
     }
@@ -207,16 +231,16 @@
               if (result == 0) {
                 ptr += 16;
               } else {
-                ptr += __builtin_ctzll(result);
+                ptr += __builtin_ctz(result);
               }
             } else {
-              ptr += __builtin_ctzll(result);
+              ptr += __builtin_ctz(result);
             }
           } else {
-            ptr += __builtin_ctzll(result);
+            ptr += __builtin_ctz(result);
           }
         } else {
-          ptr += __builtin_ctzll(result);
+          ptr += __builtin_ctz(result);
         }
       }
     }
