diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,11 @@
+[0.11.3.1] — May 2022
+
+* [Windows: Do not link against `gcc_s`](https://github.com/haskell/bytestring/pull/500)
+* [Windows: Do not link against `gcc`  when GHC >= 9.4](https://github.com/haskell/bytestring/pull/512)
+* [Refine CPP for obsolete versions of `gcc`](https://github.com/haskell/bytestring/pull/505)
+
+[0.11.3.1]: https://github.com/haskell/bytestring/compare/0.11.3.0...0.11.3.1
+
 [0.11.3.0] — February 2022
 
 * [Enhance `ShortByteString` API](https://github.com/haskell/bytestring/pull/471)
diff --git a/Data/ByteString.hs b/Data/ByteString.hs
--- a/Data/ByteString.hs
+++ b/Data/ByteString.hs
@@ -695,7 +695,7 @@
 -- | The 'mapAccumL' function behaves like a combination of 'map' and
 -- 'foldl'; it applies a function to each element of a ByteString,
 -- passing an accumulating parameter from left to right, and returning a
--- final value of this accumulator together with the new list.
+-- final value of this accumulator together with the new ByteString.
 mapAccumL :: (acc -> Word8 -> (acc, Word8)) -> acc -> ByteString -> (acc, ByteString)
 mapAccumL f acc = \(BS fp len) -> unsafeDupablePerformIO $ unsafeWithForeignPtr fp $ \a -> do
                -- see fold inlining
@@ -917,7 +917,7 @@
 {-# INLINE takeEnd #-}
 
 -- | /O(1)/ 'drop' @n xs@ returns the suffix of @xs@ after the first @n@
--- elements, or @[]@ if @n > 'length' xs@.
+-- elements, or 'empty' if @n > 'length' xs@.
 drop  :: Int -> ByteString -> ByteString
 drop n ps@(BS x l)
     | n <= 0    = ps
@@ -1034,7 +1034,7 @@
 
 -- INTERNAL:
 
--- | 'breakByte' breaks its ByteString argument at the first occurence
+-- | 'breakByte' breaks its ByteString argument at the first occurrence
 -- of the specified byte. It is more efficient than 'break' as it is
 -- implemented with @memchr(3)@. I.e.
 --
@@ -1065,7 +1065,7 @@
 {-# INLINE [1] span #-}
 
 -- | 'spanByte' breaks its ByteString argument at the first
--- occurence of a byte other than its argument. It is more efficient
+-- occurrence of a byte other than its argument. It is more efficient
 -- than 'span (==)'
 --
 -- > span  (==99) "abcd" == spanByte 99 "abcd" -- fromEnum 'c' == 99
@@ -1176,7 +1176,7 @@
 
 -- | The 'group' function takes a ByteString and returns a list of
 -- ByteStrings such that the concatenation of the result is equal to the
--- argument.  Moreover, each sublist in the result contains only equal
+-- argument.  Moreover, each string in the result contains only equal
 -- elements.  For example,
 --
 -- > group "Mississippi" = ["M","i","ss","i","ss","i","pp","i"]
@@ -1557,7 +1557,7 @@
 -- > tokenise x y = h : if null t then [] else tokenise x (drop (length x) t)
 -- >     where (h,t) = breakSubstring x y
 --
--- To skip to the first occurence of a string:
+-- To skip to the first occurrence of a string:
 --
 -- > snd (breakSubstring x y)
 --
diff --git a/Data/ByteString/Builder.hs b/Data/ByteString/Builder.hs
--- a/Data/ByteString/Builder.hs
+++ b/Data/ByteString/Builder.hs
@@ -184,7 +184,7 @@
       -- signal to the driver telling it that it is either done, has filled the
       -- current buffer, or wants to directly insert a reference to a chunk of
       -- memory. In the last two cases, the 'Builder' also returns a
-      -- continutation 'Builder' that the driver can call to fill the next
+      -- continuation 'Builder' that the driver can call to fill the next
       -- buffer. Here, we provide the two drivers that satisfy almost all use
       -- cases. See "Data.ByteString.Builder.Extra", for information
       -- about fine-tuning them.
diff --git a/Data/ByteString/Builder/Internal.hs b/Data/ByteString/Builder/Internal.hs
--- a/Data/ByteString/Builder/Internal.hs
+++ b/Data/ByteString/Builder/Internal.hs
@@ -340,7 +340,7 @@
         -- multiple times with equally sized 'BufferRange's must result in the
         -- same sequence of bytes being written. If you need mutable state,
         -- then you must allocate it anew upon each call of this function.
-        -- Moroever, this function must call the continuation once its done.
+        -- Moreover, this function must call the continuation once its done.
         -- Otherwise, concatenation of 'Builder's does not work. Finally, this
         -- function must write to all bytes that it claims it has written.
         -- Otherwise, the resulting 'Builder' is not guaranteed to be
@@ -417,7 +417,7 @@
 -- compression library or the conversion from Base64 encoded data to
 -- 8-bit data). For a 'Builder', the only way to handle and report such a
 -- failure is ignore it or call 'error'.  In contrast, 'Put' actions are
--- expressive enough to allow reportng and handling such a failure in a pure
+-- expressive enough to allow reporting and handling such a failure in a pure
 -- fashion.
 --
 -- @'Put' ()@ actions are isomorphic to 'Builder's. The functions 'putBuilder'
@@ -440,7 +440,7 @@
     -- multiple times with equally sized 'BufferRange's must result in the
     -- same sequence of bytes being written and the same value being
     -- computed. If you need mutable state, then you must allocate it anew
-    -- upon each call of this function. Moroever, this function must call
+    -- upon each call of this function. Moreover, this function must call
     -- the continuation once its done. Otherwise, monadic sequencing of
     -- 'Put's does not work. Finally, this function must write to all bytes
     -- that it claims it has written. Otherwise, the resulting 'Put' is
@@ -596,9 +596,9 @@
         --
         --   1. GHC.IO.Handle.Internals mentions in "Note [async]" that
         --      we should never do any side-effecting operations before
-        --      an interuptible operation that may raise an async. exception
+        --      an interruptible operation that may raise an async. exception
         --      as long as we are inside 'wantWritableHandle' and the like.
-        --      We possibly run the interuptible 'flushWriteBuffer' right at
+        --      We possibly run the interruptible 'flushWriteBuffer' right at
         --      the start of 'fillHandle', hence entering it a second time is
         --      not safe, as it could lead to a 'BuildStep' being run twice.
         --
diff --git a/Data/ByteString/Builder/Prim.hs b/Data/ByteString/Builder/Prim.hs
--- a/Data/ByteString/Builder/Prim.hs
+++ b/Data/ByteString/Builder/Prim.hs
@@ -439,7 +439,7 @@
 {-
   -- * Testing support
   -- | The following four functions are intended for testing use
-  -- only. They are /not/ efficient. Basic encodings are efficently executed by
+  -- only. They are /not/ efficient. Basic encodings are efficiently executed by
   -- creating 'Builder's from them using the @encodeXXX@ functions explained at
   -- the top of this module.
 
@@ -532,9 +532,9 @@
 -- influences the boundaries of the generated chunks. However, for a user of
 -- this library it is observationally equivalent, as chunk boundaries of a lazy
 -- 'L.ByteString' can only be observed through the internal interface.
--- Morevoer, we expect that all primitives write much fewer than 4kb (the
+-- Moreover, we expect that all primitives write much fewer than 4kb (the
 -- default short buffer size). Hence, it is safe to ignore the additional
--- memory spilled due to the more agressive buffer wrapping introduced by this
+-- memory spilled due to the more aggressive buffer wrapping introduced by this
 -- optimization.
 --
 {-# INLINE[1] primBounded #-}
diff --git a/Data/ByteString/Char8.hs b/Data/ByteString/Char8.hs
--- a/Data/ByteString/Char8.hs
+++ b/Data/ByteString/Char8.hs
@@ -423,7 +423,7 @@
 -- | The 'mapAccumL' function behaves like a combination of 'map' and
 -- 'foldl'; it applies a function to each element of a ByteString,
 -- passing an accumulating parameter from left to right, and returning a
--- final value of this accumulator together with the new list.
+-- final value of this accumulator together with the new ByteString.
 mapAccumL :: (acc -> Char -> (acc, Char)) -> acc -> ByteString -> (acc, ByteString)
 mapAccumL f = B.mapAccumL (\acc w -> case f acc (w2c w) of (acc', c) -> (acc', c2w c))
 
@@ -545,7 +545,7 @@
 
 -- INTERNAL:
 
--- | 'breakChar' breaks its ByteString argument at the first occurence
+-- | 'breakChar' breaks its ByteString argument at the first occurrence
 -- of the specified char. It is more efficient than 'break' as it is
 -- implemented with @memchr(3)@. I.e.
 --
diff --git a/Data/ByteString/Internal.hs b/Data/ByteString/Internal.hs
--- a/Data/ByteString/Internal.hs
+++ b/Data/ByteString/Internal.hs
@@ -595,7 +595,7 @@
 -- | Given the maximum size needed and a function to make the contents
 -- of a ByteString, createAndTrim makes the 'ByteString'. The generating
 -- function is required to return the actual final size (<= the maximum
--- size), and the resulting byte array is realloced to this size.
+-- size), and the resulting byte array is reallocated to this size.
 --
 -- createAndTrim is the main mechanism for creating custom, efficient
 -- ByteString functions, using Haskell or C functions to fill the space.
diff --git a/Data/ByteString/Lazy.hs b/Data/ByteString/Lazy.hs
--- a/Data/ByteString/Lazy.hs
+++ b/Data/ByteString/Lazy.hs
@@ -773,7 +773,7 @@
             | otherwise      = (0, Chunk (S.takeEnd (fromIntegral n) c) cs)
 
 -- | /O(n\/c)/ 'drop' @n xs@ returns the suffix of @xs@ after the first @n@
--- elements, or @[]@ if @n > 'length' xs@.
+-- elements, or 'empty' if @n > 'length' xs@.
 drop  :: Int64 -> ByteString -> ByteString
 drop i p | i <= 0 = p
 drop i cs0 = drop' i cs0
@@ -962,7 +962,7 @@
 --
 
 {-
--- | 'breakByte' breaks its ByteString argument at the first occurence
+-- | 'breakByte' breaks its ByteString argument at the first occurrence
 -- of the specified byte. It is more efficient than 'break' as it is
 -- implemented with @memchr(3)@. I.e.
 --
@@ -979,7 +979,7 @@
                         in (x : xs', xs'')
 
 -- | 'spanByte' breaks its ByteString argument at the first
--- occurence of a byte other than its argument. It is more efficient
+-- occurrence of a byte other than its argument. It is more efficient
 -- than 'span (==)'
 --
 -- > span  (==99) "abcd" == spanByte 99 "abcd" -- fromEnum 'c' == 99
@@ -1070,7 +1070,7 @@
 
 -- | The 'group' function takes a ByteString and returns a list of
 -- ByteStrings such that the concatenation of the result is equal to the
--- argument.  Moreover, each sublist in the result contains only equal
+-- argument.  Moreover, each string in the result contains only equal
 -- elements.  For example,
 --
 -- > group "Mississippi" = ["M","i","ss","i","ss","i","pp","i"]
diff --git a/Data/ByteString/Lazy/Char8.hs b/Data/ByteString/Lazy/Char8.hs
--- a/Data/ByteString/Lazy/Char8.hs
+++ b/Data/ByteString/Lazy/Char8.hs
@@ -571,7 +571,7 @@
 {-# INLINE spanEnd #-}
 
 {-
--- | 'breakChar' breaks its ByteString argument at the first occurence
+-- | 'breakChar' breaks its ByteString argument at the first occurrence
 -- of the specified Char. It is more efficient than 'break' as it is
 -- implemented with @memchr(3)@. I.e.
 --
@@ -582,7 +582,7 @@
 {-# INLINE breakChar #-}
 
 -- | 'spanChar' breaks its ByteString argument at the first
--- occurence of a Char other than its argument. It is more efficient
+-- occurrence of a Char other than its argument. It is more efficient
 -- than 'span (==)'
 --
 -- > span  (=='c') "abcd" == spanByte 'c' "abcd"
diff --git a/Data/ByteString/Short/Internal.hs b/Data/ByteString/Short/Internal.hs
--- a/Data/ByteString/Short/Internal.hs
+++ b/Data/ByteString/Short/Internal.hs
@@ -565,7 +565,7 @@
       writeWord8Array mba i w
       go mba (i+1) ws
 
--- Unpacking bytestrings into lists effeciently is a tradeoff: on the one hand
+-- Unpacking bytestrings into lists efficiently is a tradeoff: on the one hand
 -- we would like to write a tight loop that just blats the list into memory, on
 -- the other hand we want it to be unpacked lazily so we don't end up with a
 -- massive list data structure in memory.
@@ -1036,7 +1036,7 @@
 takeWhileEnd :: (Word8 -> Bool) -> ShortByteString -> ShortByteString
 takeWhileEnd f = \sbs -> drop (findFromEndUntil (not . f) sbs) sbs
 
--- | /O(n)/ 'drop' @n@ @xs@ returns the suffix of @xs@ after the first n elements, or @[]@ if @n > 'length' xs@.
+-- | /O(n)/ 'drop' @n@ @xs@ returns the suffix of @xs@ after the first n elements, or 'empty' if @n > 'length' xs@.
 --
 -- Note: copies the entire byte array
 --
@@ -1354,7 +1354,7 @@
 -- > tokenise x y = h : if null t then [] else tokenise x (drop (length x) t)
 -- >     where (h,t) = breakSubstring x y
 --
--- To skip to the first occurence of a string:
+-- To skip to the first occurrence of a string:
 --
 -- > snd (breakSubstring x y)
 --
diff --git a/bytestring.cabal b/bytestring.cabal
--- a/bytestring.cabal
+++ b/bytestring.cabal
@@ -1,5 +1,5 @@
 Name:                bytestring
-Version:             0.11.3.0
+Version:             0.11.3.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)
@@ -29,8 +29,8 @@
     serialisation or pretty printing.
     .
     There is also a 'ShortByteString' type which has a lower memory overhead
-    and can can be converted to or from a 'ByteString', but supports very few
-    other operations. It is suitable for keeping many short strings in memory.
+    and can be converted to or from a 'ByteString'. It is suitable for keeping
+    many short strings in memory.
     .
     'ByteString's are not designed for Unicode. For Unicode strings you should
     use the 'Text' type from the @text@ package.
@@ -131,11 +131,10 @@
   -- DNDEBUG disables asserts in cbits/
   cc-options:        -std=c11 -DNDEBUG=1
  
-  -- Required, due to the following issues:
-  -- * https://gitlab.haskell.org/ghc/ghc/-/issues/20525#note_385580
-  -- * https://gitlab.haskell.org/ghc/ghc/-/issues/19417
-  if os(windows)
-    extra-libraries:  gcc_s gcc
+  -- No need to link to libgcc on ghc-9.4 and later which uses a clang-based
+  -- toolchain.
+  if os(windows) && impl(ghc < 9.3)
+    extra-libraries:  gcc
 
   include-dirs:      include
   includes:          fpstring.h
diff --git a/cbits/fpstring.c b/cbits/fpstring.c
--- a/cbits/fpstring.c
+++ b/cbits/fpstring.c
@@ -38,7 +38,7 @@
 #include <stdint.h>
 #include <stdbool.h>
 
-#if defined(__x86_64__) && (__GNUC__ >= 6 || defined(__clang_major__)) && !defined(__STDC_NO_ATOMICS__)
+#if defined(__x86_64__) && (__GNUC__ >= 7 || __GNUC__ == 6 && __GNUC_MINOR__ >= 3 || defined(__clang_major__)) && !defined(__STDC_NO_ATOMICS__)
 #include <stdatomic.h>
 #define USE_SIMD_COUNT
 #endif
@@ -106,7 +106,7 @@
     return qsort(p, len, 1, fps_compare);
 }
 
-/* count the number of occurences of a char in a string */
+/* count the number of occurrences of a char in a string */
 size_t fps_count_naive(unsigned char *str, size_t len, unsigned char w) {
     size_t c;
     for (c = 0; len-- != 0; ++str)
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
@@ -38,7 +38,7 @@
 #include <emmintrin.h>
 #include <immintrin.h>
 #include <cpuid.h>
-#if (__GNUC__ >= 6 || defined(__clang_major__)) && !defined(__STDC_NO_ATOMICS__)
+#if (__GNUC__ >= 7 || __GNUC__ == 6 && __GNUC_MINOR__ >= 3 || defined(__clang_major__)) && !defined(__STDC_NO_ATOMICS__)
 #include <tmmintrin.h>
 #include <stdatomic.h>
 #else
