Skip to content

Commit

Permalink
Replace isnan() with better comparisons (isgreater(), etc.)
Browse files Browse the repository at this point in the history
The standard isnan() function is defined to never throw FP exceptions
even when the argument is a "signaling" NaN. This makes isnan() more
expensive than (x != x) expression unless the compiler flag
'-fno-signaling-nans' is given.

Introduce functions isNaN(), isNonnegative(), isPositive(),
sumPositiveValues() and compareRealNumbers(), and replace isnan() in
htop's codebase with the new functions. These functions utilize
isgreater() and isgreaterequal() comparisons, which do not throw FP
exceptions on "quiet" NaNs, which htop uses extensively.

With isnan() removed, there is no need to suppress the warning
'-Wno-c11-extensions' in FreeBSD. Remove the code from 'configure.ac'.

Signed-off-by: Kang-Che Sung <[email protected]>
  • Loading branch information
Explorer09 authored and BenBE committed Aug 18, 2023
1 parent c6fd64f commit b416433
Show file tree
Hide file tree
Showing 22 changed files with 204 additions and 141 deletions.
3 changes: 2 additions & 1 deletion BatteryMeter.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This meter written by Ian P. Hands ([email protected], [email protected]).
#include <math.h>

#include "CRT.h"
#include "Macros.h"
#include "Object.h"
#include "Platform.h"
#include "XUtils.h"
Expand All @@ -27,7 +28,7 @@ static void BatteryMeter_updateValues(Meter* this) {

Platform_getBattery(&percent, &isOnAC);

if (isnan(percent)) {
if (!isNonnegative(percent)) {
this->values[0] = NAN;
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "N/A");
return;
Expand Down
26 changes: 13 additions & 13 deletions CPUMeter.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ in the source distribution for its full text.

#include "CPUMeter.h"

#include <math.h>
#include <stdlib.h>
#include <string.h>

#include "CRT.h"
#include "Macros.h"
#include "Object.h"
#include "Platform.h"
#include "ProcessList.h"
Expand Down Expand Up @@ -71,7 +71,7 @@ static void CPUMeter_updateValues(Meter* this) {
}

double percent = Platform_setCPUValues(this, cpu);
if (isnan(percent)) {
if (!isNonnegative(percent)) {
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "offline");
return;
}
Expand All @@ -86,17 +86,17 @@ static void CPUMeter_updateValues(Meter* this) {

if (settings->showCPUFrequency) {
double cpuFrequency = this->values[CPU_METER_FREQUENCY];
if (isnan(cpuFrequency)) {
xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A");
} else {
if (isNonnegative(cpuFrequency)) {
xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "%4uMHz", (unsigned)cpuFrequency);
} else {
xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A");
}
}

#ifdef BUILD_WITH_CPU_TEMP
if (settings->showCPUTemperature) {
double cpuTemperature = this->values[CPU_METER_TEMPERATURE];
if (isnan(cpuTemperature))
if (isNaN(cpuTemperature))
xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "N/A");
else if (settings->degreeFahrenheit)
xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%3d%sF", (int)(cpuTemperature * 9 / 5 + 32), CRT_degreeSign);
Expand Down Expand Up @@ -146,12 +146,12 @@ static void CPUMeter_display(const Object* cast, RichString* out) {
len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_SOFTIRQ]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "si:");
RichString_appendnAscii(out, CRT_colors[CPU_SOFTIRQ], buffer, len);
if (!isnan(this->values[CPU_METER_STEAL])) {
if (isNonnegative(this->values[CPU_METER_STEAL])) {
len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_STEAL]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "st:");
RichString_appendnAscii(out, CRT_colors[CPU_STEAL], buffer, len);
}
if (!isnan(this->values[CPU_METER_GUEST])) {
if (isNonnegative(this->values[CPU_METER_GUEST])) {
len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_GUEST]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "gu:");
RichString_appendnAscii(out, CRT_colors[CPU_GUEST], buffer, len);
Expand All @@ -166,7 +166,7 @@ static void CPUMeter_display(const Object* cast, RichString* out) {
len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_NICE]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "low:");
RichString_appendnAscii(out, CRT_colors[CPU_NICE_TEXT], buffer, len);
if (!isnan(this->values[CPU_METER_IRQ])) {
if (isNonnegative(this->values[CPU_METER_IRQ])) {
len = xSnprintf(buffer, sizeof(buffer), "%5.1f%% ", this->values[CPU_METER_IRQ]);
RichString_appendAscii(out, CRT_colors[METER_TEXT], "vir:");
RichString_appendnAscii(out, CRT_colors[CPU_GUEST], buffer, len);
Expand All @@ -176,10 +176,10 @@ static void CPUMeter_display(const Object* cast, RichString* out) {
if (settings->showCPUFrequency) {
char cpuFrequencyBuffer[10];
double cpuFrequency = this->values[CPU_METER_FREQUENCY];
if (isnan(cpuFrequency)) {
len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A ");
} else {
if (isNonnegative(cpuFrequency)) {
len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "%4uMHz ", (unsigned)cpuFrequency);
} else {
len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), "N/A ");
}
RichString_appendAscii(out, CRT_colors[METER_TEXT], "freq: ");
RichString_appendnWide(out, CRT_colors[METER_VALUE], cpuFrequencyBuffer, len);
Expand All @@ -189,7 +189,7 @@ static void CPUMeter_display(const Object* cast, RichString* out) {
if (settings->showCPUTemperature) {
char cpuTemperatureBuffer[10];
double cpuTemperature = this->values[CPU_METER_TEMPERATURE];
if (isnan(cpuTemperature)) {
if (isNaN(cpuTemperature)) {
len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "N/A");
} else if (settings->degreeFahrenheit) {
len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%5.1f%sF", cpuTemperature * 9 / 5 + 32, CRT_degreeSign);
Expand Down
11 changes: 6 additions & 5 deletions FileDescriptorMeter.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ in the source distribution for its full text.
#include <string.h>

#include "CRT.h"
#include "Macros.h"
#include "Meter.h"
#include "Object.h"
#include "Platform.h"
#include "RichString.h"
#include "XUtils.h"


#define FD_EFFECTIVE_UNLIMITED(x) ((x) > (1<<30))
#define FD_EFFECTIVE_UNLIMITED(x) (!isgreaterequal((double)(1<<30), (x)))

static const int FileDescriptorMeter_attributes[] = {
FILE_DESCRIPTOR_USED,
Expand Down Expand Up @@ -67,9 +68,9 @@ static void FileDescriptorMeter_updateValues(Meter* this) {
}
}

if (isnan(this->values[0])) {
if (!isNonnegative(this->values[0])) {
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "unknown/unknown");
} else if (isnan(this->values[1]) || FD_EFFECTIVE_UNLIMITED(this->values[1])) {
} else if (FD_EFFECTIVE_UNLIMITED(this->values[1])) {
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.0lf/unlimited", this->values[0]);
} else {
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.0lf/%.0lf", this->values[0], this->values[1]);
Expand All @@ -81,7 +82,7 @@ static void FileDescriptorMeter_display(const Object* cast, RichString* out) {
char buffer[50];
int len;

if (isnan(this->values[0])) {
if (!isNonnegative(this->values[0])) {
RichString_appendAscii(out, CRT_colors[METER_TEXT], "unknown");
return;
}
Expand All @@ -91,7 +92,7 @@ static void FileDescriptorMeter_display(const Object* cast, RichString* out) {
RichString_appendnAscii(out, CRT_colors[FILE_DESCRIPTOR_USED], buffer, len);

RichString_appendAscii(out, CRT_colors[METER_TEXT], " max: ");
if (isnan(this->values[1]) || FD_EFFECTIVE_UNLIMITED(this->values[1])) {
if (FD_EFFECTIVE_UNLIMITED(this->values[1])) {
RichString_appendAscii(out, CRT_colors[FILE_DESCRIPTOR_MAX], "unlimited");
} else {
len = xSnprintf(buffer, sizeof(buffer), "%.0lf", this->values[1]);
Expand Down
20 changes: 20 additions & 0 deletions Macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#define HEADER_Macros

#include <assert.h> // IWYU pragma: keep
#include <math.h>
#include <stdbool.h>

#ifndef MINIMUM
#define MINIMUM(a, b) ((a) < (b) ? (a) : (b))
Expand Down Expand Up @@ -98,6 +100,24 @@
#define IGNORE_WCASTQUAL_END
#endif

/* Cheaper function for checking NaNs. Unlike the standard isnan(), this may
throw an FP exception on a "signaling" NaN.
(ISO/IEC TS 18661-1 and the C23 standard stated that isnan() throws no
exceptions even with a "signaling" NaN) */
static inline bool isNaN(double x) {
return !isgreaterequal(x, x);
}

/* Checks if x is nonnegative. Returns false if x is NaN. */
static inline bool isNonnegative(double x) {
return isgreaterequal(x, 0.0);
}

/* Checks if x is positive. Returns false if x is NaN. */
static inline bool isPositive(double x) {
return isgreater(x, 0.0);
}

/* This subtraction is used by Linux / NetBSD / OpenBSD for calculation of CPU usage items. */
static inline unsigned long long saturatingSub(unsigned long long a, unsigned long long b) {
return a > b ? a - b : 0;
Expand Down
10 changes: 5 additions & 5 deletions MemoryMeter.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ in the source distribution for its full text.
#include <stddef.h>

#include "CRT.h"
#include "Macros.h"
#include "Object.h"
#include "Platform.h"
#include "RichString.h"
Expand Down Expand Up @@ -42,9 +43,8 @@ static void MemoryMeter_updateValues(Meter* this) {

/* we actually want to show "used + compressed" */
double used = this->values[MEMORY_METER_USED];
if (!isnan(this->values[MEMORY_METER_COMPRESSED])) {
if (isPositive(this->values[MEMORY_METER_COMPRESSED]))
used += this->values[MEMORY_METER_COMPRESSED];
}

written = Meter_humanUnit(buffer, used, size);
METER_BUFFER_CHECK(buffer, size, written);
Expand All @@ -71,14 +71,14 @@ static void MemoryMeter_display(const Object* cast, RichString* out) {
RichString_appendAscii(out, CRT_colors[MEMORY_BUFFERS_TEXT], buffer);

/* shared memory is not supported on all platforms */
if (!isnan(this->values[MEMORY_METER_SHARED])) {
if (isNonnegative(this->values[MEMORY_METER_SHARED])) {
Meter_humanUnit(buffer, this->values[MEMORY_METER_SHARED], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " shared:");
RichString_appendAscii(out, CRT_colors[MEMORY_SHARED], buffer);
}

/* compressed memory is not supported on all platforms */
if (!isnan(this->values[MEMORY_METER_COMPRESSED])) {
if (isNonnegative(this->values[MEMORY_METER_COMPRESSED])) {
Meter_humanUnit(buffer, this->values[MEMORY_METER_COMPRESSED], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " compressed:");
RichString_appendAscii(out, CRT_colors[MEMORY_COMPRESSED], buffer);
Expand All @@ -89,7 +89,7 @@ static void MemoryMeter_display(const Object* cast, RichString* out) {
RichString_appendAscii(out, CRT_colors[MEMORY_CACHE], buffer);

/* available memory is not supported on all platforms */
if (!isnan(this->values[MEMORY_METER_AVAILABLE])) {
if (isNonnegative(this->values[MEMORY_METER_AVAILABLE])) {
Meter_humanUnit(buffer, this->values[MEMORY_METER_AVAILABLE], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " available:");
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
Expand Down
5 changes: 1 addition & 4 deletions Meter.c
Original file line number Diff line number Diff line change
Expand Up @@ -333,10 +333,7 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) {
if (Meter_comprisedValues(this)) {
data->values[nValues - 1] = (this->curItems > 0) ? this->values[this->curItems - 1] : 0.0;
} else {
double value = 0.0;
for (uint8_t i = 0; i < this->curItems; i++)
value += !isnan(this->values[i]) ? this->values[i] : 0;
data->values[nValues - 1] = value;
data->values[nValues - 1] = sumPositiveValues(this->values, this->curItems);
}
}

Expand Down
4 changes: 2 additions & 2 deletions Process.c
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ void Process_printRate(RichString* str, double rate, bool coloring) {
processMegabytesColor = CRT_colors[PROCESS];
}

if (isnan(rate)) {
if (!isNonnegative(rate)) {
RichString_appendAscii(str, shadowColor, " N/A ");
} else if (rate < 0.005) {
int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate);
Expand Down Expand Up @@ -790,7 +790,7 @@ void Process_printLeftAlignedField(RichString* str, int attr, const char* conten
}

void Process_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr) {
if (val >= 0) {
if (isNonnegative(val)) {
if (val < 0.05F)
*attr = CRT_colors[PROCESS_SHADOW];
else if (val >= 99.9F)
Expand Down
5 changes: 3 additions & 2 deletions SwapMeter.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ in the source distribution for its full text.
#include <stddef.h>

#include "CRT.h"
#include "Macros.h"
#include "Object.h"
#include "Platform.h"
#include "RichString.h"
Expand Down Expand Up @@ -51,13 +52,13 @@ static void SwapMeter_display(const Object* cast, RichString* out) {
RichString_appendAscii(out, CRT_colors[METER_TEXT], " used:");
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);

if (!isnan(this->values[SWAP_METER_CACHE])) {
if (isNonnegative(this->values[SWAP_METER_CACHE])) {
Meter_humanUnit(buffer, this->values[SWAP_METER_CACHE], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " cache:");
RichString_appendAscii(out, CRT_colors[SWAP_CACHE], buffer);
}

if (!isnan(this->values[SWAP_METER_FRONTSWAP])) {
if (isNonnegative(this->values[SWAP_METER_FRONTSWAP])) {
Meter_humanUnit(buffer, this->values[SWAP_METER_FRONTSWAP], sizeof(buffer));
RichString_appendAscii(out, CRT_colors[METER_TEXT], " frontswap:");
RichString_appendAscii(out, CRT_colors[SWAP_FRONTSWAP], buffer);
Expand Down
13 changes: 13 additions & 0 deletions XUtils.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ in the source distribution for its full text.
#include <unistd.h>

#include "CRT.h"
#include "Macros.h"


void fail(void) {
Expand Down Expand Up @@ -337,3 +338,15 @@ ssize_t full_write(int fd, const void* buf, size_t count) {

return written;
}

/* Computes the sum of all positive floating point values in an array.
NaN values in the array are skipped. The returned sum will always be
nonnegative. */
double sumPositiveValues(const double* array, size_t count) {
double sum = 0.0;
for (size_t i = 0; i < count; i++) {
if (isPositive(array[i]))
sum += array[i];
}
return sum;
}
5 changes: 5 additions & 0 deletions XUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,9 @@ ssize_t xReadfileat(openat_arg_t dirfd, const char* pathname, void* buffer, size
ATTR_ACCESS3_R(2, 3)
ssize_t full_write(int fd, const void* buf, size_t count);

/* Computes the sum of all positive floating point values in an array.
NaN values in the array are skipped. The returned sum will always be
nonnegative. */
double sumPositiveValues(const double* array, size_t count);

#endif
42 changes: 29 additions & 13 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -203,21 +203,42 @@ AC_COMPILE_IFELSE([
CFLAGS="$old_CFLAGS"

AC_MSG_CHECKING(for NaN support)
AC_RUN_IFELSE([
dnl Note: AC_RUN_IFELSE does not try compiling the program at all when
dnl $cross_compiling is 'yes'.
AC_LINK_IFELSE([
AC_LANG_PROGRAM(
[[
#include <math.h>
#include <math.h>
]],
[[
double x = NAN; return !isnan(x);
double x = NAN;
/* Both should evaluate to false -> 0 (exit success) */
return isgreater(x, x) || isgreaterequal(x, x);
]]
)],
[AC_MSG_RESULT(yes)],
[
[flag_finite_math_only=unknown
if test "$cross_compiling" = yes; then
AC_COMPILE_IFELSE([
AC_LANG_SOURCE([[
/* __FINITE_MATH_ONLY__ is documented in Clang. */
#ifdef __FINITE_MATH_ONLY__
#error "should not enable -ffinite-math-only"
#endif
]])],
AC_MSG_RESULT([assume yes (cross compiling)]),
flag_finite_math_only=yes)
elif ./conftest$EXEEXT >&AS_MESSAGE_LOG_FD; then
flag_finite_math_only=no
AC_MSG_RESULT(yes)
else
flag_finite_math_only=yes
fi
if test "$flag_finite_math_only" = yes; then
AC_MSG_RESULT(no)
AC_MSG_WARN([Compiler does not respect NaN, some functionality might break; consider using '-fno-finite-math-only'])
],
[AC_MSG_RESULT(skipped)])
AC_MSG_WARN([runtime behavior with NaN is not compliant - some functionality might break; consider using '-fno-finite-math-only'])
fi],
[AC_MSG_RESULT(no)
AC_MSG_ERROR([can not find required macros: NAN, isgreater() and isgreaterequal()])])

# ----------------------------------------------------------------------

Expand Down Expand Up @@ -700,11 +721,6 @@ AM_CFLAGS="\
-Wunused\
-Wwrite-strings"

# FreeBSD uses C11 _Generic in its isnan implementation, even with -std=c99
if test "$my_htop_platform" = freebsd; then
AM_CFLAGS="$AM_CFLAGS -Wno-c11-extensions"
fi

dnl https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
AC_DEFUN([AX_CHECK_COMPILE_FLAG],
[
Expand Down

0 comments on commit b416433

Please sign in to comment.