This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
S_lossless_NV_to_IV(): skip Perl_isnan
authorDavid Mitchell <davem@iabyn.com>
Thu, 27 Aug 2020 16:08:03 +0000 (17:08 +0100)
committerDavid Mitchell <davem@iabyn.com>
Thu, 27 Aug 2020 16:21:12 +0000 (17:21 +0100)
This inline function was added by v5.31.0-27-g3a019afd6f to consolidate
similar code in several places, like pp_add(). It also avoided undefined
behaviour, as seen by ASan, by no longer unconditionally trying to cast
an NV to IV - ASan would complain when nv was -Inf for example.

However that commit introduced a performance regression into common
numeric operators like pp_and(). This commit partially claws back
performance by skipping the initial test of 'skip if Nan' which called
Perl_isnan(). Instead, except on systems where NAN_COMPARE_BROKEN is
true, it relies on NaN being compared to anything always being false,
and simply rearranges existing conditions nv < IV_MIN etc to be
nv >= IV_MIN so that any NaN comparison will trigger a false return.

This claws back about half the performance loss. The rest seems
unavoidable, since the two range tests for IV_MIN..IV_MAX are an
unavoidable part of avoiding undefined behaviour.

inline.h

index 6fbd5ab..29ad5a2 100644 (file)
--- a/inline.h
+++ b/inline.h
@@ -1962,15 +1962,17 @@ S_lossless_NV_to_IV(const NV nv, IV *ivp)
 
     PERL_ARGS_ASSERT_LOSSLESS_NV_TO_IV;
 
-#  if  defined(Perl_isnan)
-
+#  if defined(NAN_COMPARE_BROKEN) && defined(Perl_isnan)
+    /* Normally any comparison with a NaN returns false; if we can't rely
+     * on that behaviour, check explicitly */
     if (UNLIKELY(Perl_isnan(nv))) {
         return FALSE;
     }
-
 #  endif
 
-    if (UNLIKELY(nv < IV_MIN) || UNLIKELY(nv > IV_MAX)) {
+    /* Written this way so that with an always-false NaN comparison we
+     * return false */
+    if (!(LIKELY(nv >= IV_MIN) && LIKELY(nv <= IV_MAX))) {
         return FALSE;
     }