summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libraries/ghc-prim/cbits/ctz.c18
1 files changed, 17 insertions, 1 deletions
diff --git a/libraries/ghc-prim/cbits/ctz.c b/libraries/ghc-prim/cbits/ctz.c
index cc420b9acd..f68f628bd3 100644
--- a/libraries/ghc-prim/cbits/ctz.c
+++ b/libraries/ghc-prim/cbits/ctz.c
@@ -31,7 +31,23 @@ hs_ctz32(StgWord x)
StgWord
hs_ctz64(StgWord64 x)
{
-#if SIZEOF_UNSIGNED_LONG == 8
+#if defined(__GNUC__) && defined(i386_HOST_ARCH)
+ /* On Linux/i386, the 64bit `__builtin_ctzll()` instrinsic doesn't
+ get inlined by GCC but rather a short `__ctzdi2` runtime function
+ is inserted when needed into compiled object files.
+
+ This workaround forces GCC on 32bit x86 to to express `hs_ctz64` in
+ terms of the 32bit `__builtin_ctz()` (this is no loss, as there's no
+ 64bit BSF instruction on i686 anyway) and thus avoid the problematic
+ out-of-line runtime function.
+ */
+
+ if (!x) return 64;
+
+ return ((uint32_t)x ? __builtin_ctz((uint32_t)x)
+ : (__builtin_ctz(x >> 32) + 32));
+
+#elif SIZEOF_UNSIGNED_LONG == 8
return x ? __builtin_ctzl(x) : 64;
#elif SIZEOF_UNSIGNED_LONG_LONG == 8
return x ? __builtin_ctzll(x) : 64;