Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial code improvements with respect to Clang 19 warnings #1516

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CategoriesPanel.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ static void CategoriesPanel_delete(Object* object) {
}

static void CategoriesPanel_makeMetersPage(CategoriesPanel* this) {
size_t columns = HeaderLayout_getColumns(this->scr->header->headerLayout);
unsigned int columns = HeaderLayout_getColumns(this->scr->header->headerLayout);
MetersPanel** meterPanels = xMallocArray(columns, sizeof(MetersPanel*));
Settings* settings = this->host->settings;

for (size_t i = 0; i < columns; i++) {
for (unsigned int i = 0; i < columns; i++) {
char titleBuffer[32];
xSnprintf(titleBuffer, sizeof(titleBuffer), "Column %zu", i + 1);
xSnprintf(titleBuffer, sizeof(titleBuffer), "Column %u", i + 1);
Comment on lines -45 to +51
Copy link
Member

@BenBE BenBE Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one is a step backwards. Stick with size_t.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't understand what you mean. What's the reason of keeping the size_t?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two-fold:

  1. It's a number of items (columns)
  2. unsigned int looks kinda unwieldy IMHO.
  3. For the format string %zu is much more portable than uint32_t which would require you to use %"PRIu32" instead of just %u.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BenBE Well, I have a different view on this:

  1. Object sizes with no likelihood of overflowing an int type would not need size_t (that is, unsigned long) for storing.
  2. In x86-64, incrementing and comparing int types are slightly cheaper than comparing long or size_t (in terms of code size). When int types can suffice, using long would make the code one-byte-per-operation larger.
  3. Header column count is unlikely to exceed termical column count (COLS), and the latter is bound to signed int type already.
  4. And no, we don't need PRIu32 here. It's unsigned int, not uint32_t.

This case is slightly different from string lengths. My view is string lengths in size_t can serve as a defence in depth, where the user is possible to feed a 4 GiB large string in a config file. But if the object size would be bound by any other factor, then size_t might be a waste.

(By the way, I did read this article from EWONTFIX when I write my changes.)

I hope htop developers can reconsider.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal opinion following:

@BenBE Well, I have a different view on this:

  1. Object sizes with no likelihood of overflowing an int type would not need size_t (that is, unsigned long) for storing.

Nonetheless size_t is less about the number of entities itself, but that you are counting entities.

Otherwise you could argue that "this hashtable implementation only needs short for its size, because I never store more than 65535 elements in it".

I know, that example is hyperbole, but follows that same logic. When I ask for size_t it's really little about "how many", but the "what" (AKA semantics).

  1. In x86-64, incrementing and comparing int types are slightly cheaper than comparing long or size_t (in terms of code size). When int types can suffice, using long would make the code one-byte-per-operation larger.

That might be true for x86_64, but that's not the only platform we need to support. There's at least ARM, ARM64, MIPS, and probably several others, where htop is runing. Unless performance really IS a bottleneck for these functions (AFAICS it's not), priority should be on correctness, not code size or performance.

  1. Header column count is unlikely to exceed terminal column count (COLS), and the latter is bound to signed int type already.

See arguments from the article you linked.

That limit is implied by the current interface from ncurses, but a future version could just switch to size_t for its column and row counts (potentially breaking BC at that point); there are good reasons like huge screens where terminals COULD have that many columns/rows.

  1. And no, we don't need PRIu32 here. It's unsigned int, not uint32_t.

k, ACK on that. But that's only a minor note regarding the chosen type signature.

This case is slightly different from string lengths. My view is string lengths in size_t can serve as a defence in depth, where the user is possible to feed a 4 GiB large string in a config file. But if the object size would be bound by any other factor, then size_t might be a waste.

Allocations are typically backed by 4 KiB pages at minimum, with the usual memory manager implementation pre-allocating some memory to handle sudden spikes. Thus unless the savings are major or we have massive amounts of objects (where storing UTF-8 instead of UTF-16 saves several hundreds of megabytes (like with some browsers) there's usually little need to save every last byte. Furthermore, by storing smaller data types you sometimes may make the alignment of the data worse, thus actually reducing performance.

From my experience I can tell, that I've seen real-life cases of where uint16_t vs. uint32_t was actually harmful due to alignment and memory access patterns. For 32 bit values on a 64 bit architecture, similar considerations apply.

(By the way, I did read this article from EWONTFIX when I write my changes.)

FWIW, I personally side with the author.

I hope htop developers can reconsider.

@cgzones @fasterit @ginggs @natoscott : Your opinion on this matter of size_t vs unsigned int?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to stick with size_t here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seconded
/DLange

Copy link
Contributor Author

@Explorer09 Explorer09 Aug 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BenBE @natoscott @fasterit
This commit was to fix the implicit downcast warnings from HeaderLayout_getColumns() by Clang 19 (the messages was: "implicit conversion loses integer precision: 'size_t' (aka 'unsigned long') to 'unsigned int' [-Wshorten-64-to-32]"). HeaderLayout_getColumns() was an inline function that returns a size_t that, in certain caller codes, has to downcast to int because terminal column variables are in int type to follow curses API.

With this background, I would like some clarifications on which decision to make:
(A) Keep HeaderLayout_getColumns() return value in size_t. Use size_t for local variables that hold the return values of HeaderLayout_getColumns(), and add explicit downcasts when needed (e.g. when used in the context of terminal column widths).
(B) Let HeaderLayout_getColumns() return an unsigned int. Keep using int type for places like terminal columns. But when using in array iterators (like in this example code you guys are commented on), upcast to size_t.
(C) Let HeaderLayout_getColumns() return an unsigned int. Also use unsigned int for array iterators that would never cross the bound of HeaderLayout_getColumns(). This is my original proposal.

EDIT: Just a reminder that the (A) option is the least preferred to me as the downcasts can hide potential errors when the original "getColumns" values are out of bounds. Although assertions may be added to ensure the numbers are in bound, they still look like an overkill solution to me (making the problem more complicated than necessary).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A.

meterPanels[i] = MetersPanel_new(settings, titleBuffer, this->header->columns[i], this->scr);

if (i != 0) {
Expand Down
12 changes: 6 additions & 6 deletions Header.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ void Header_delete(Header* this) {
}

void Header_setLayout(Header* this, HeaderLayout hLayout) {
size_t oldColumns = HeaderLayout_getColumns(this->headerLayout);
size_t newColumns = HeaderLayout_getColumns(hLayout);
unsigned int oldColumns = HeaderLayout_getColumns(this->headerLayout);
unsigned int newColumns = HeaderLayout_getColumns(hLayout);

this->headerLayout = hLayout;

Expand All @@ -61,11 +61,11 @@ void Header_setLayout(Header* this, HeaderLayout hLayout) {

if (newColumns > oldColumns) {
this->columns = xReallocArray(this->columns, newColumns, sizeof(Vector*));
for (size_t i = oldColumns; i < newColumns; i++)
for (unsigned int i = oldColumns; i < newColumns; i++)
this->columns[i] = Vector_new(Class(Meter), true, DEFAULT_SIZE);
} else {
// move meters from to-be-deleted columns into last one
for (size_t i = newColumns; i < oldColumns; i++) {
for (unsigned int i = newColumns; i < oldColumns; i++) {
for (int j = this->columns[i]->items - 1; j >= 0; j--) {
Vector_add(this->columns[newColumns - 1], Vector_take(this->columns[i], j));
}
Expand Down Expand Up @@ -198,7 +198,7 @@ void Header_draw(const Header* this) {
for (int y = 0; y < height; y++) {
mvhline(y, 0, ' ', COLS);
}
const int numCols = HeaderLayout_getColumns(this->headerLayout);
const int numCols = (int)HeaderLayout_getColumns(this->headerLayout);
const int width = COLS - 2 * pad - (numCols - 1);
int x = pad;
float roundingLoss = 0.0F;
Expand Down Expand Up @@ -254,7 +254,7 @@ void Header_updateData(Header* this) {
* Returns the number of columns to span, i.e. if the direct neighbor is occupied 1.
*/
static int calcColumnWidthCount(const Header* this, const Meter* curMeter, const int pad, const unsigned int curColumn, const int curHeight) {
for (size_t i = curColumn + 1; i < HeaderLayout_getColumns(this->headerLayout); i++) {
for (unsigned int i = curColumn + 1; i < HeaderLayout_getColumns(this->headerLayout); i++) {
const Vector* meters = this->columns[i];

int height = pad;
Expand Down
2 changes: 1 addition & 1 deletion Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ typedef struct Header_ {
int height;
} Header;

#define Header_forEachColumn(this_, i_) for (size_t (i_)=0, H_fEC_numColumns_ = HeaderLayout_getColumns((this_)->headerLayout); (i_) < H_fEC_numColumns_; ++(i_))
#define Header_forEachColumn(this_, i_) for (unsigned int (i_)=0, H_fEC_numColumns_ = HeaderLayout_getColumns((this_)->headerLayout); (i_) < H_fEC_numColumns_; ++(i_))

Header* Header_new(Machine* host, HeaderLayout hLayout);

Expand Down
2 changes: 1 addition & 1 deletion HeaderLayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ static const struct {
[HF_FOUR_25_25_25_25] = { 4, { 25, 25, 25, 25 }, "four_25_25_25_25", "4 columns - 25/25/25/25", },
};

static inline size_t HeaderLayout_getColumns(HeaderLayout hLayout) {
static inline unsigned int HeaderLayout_getColumns(HeaderLayout hLayout) {
/* assert the layout is initialized */
assert(0 <= hLayout);
assert(hLayout < LAST_HEADER_LAYOUT);
Expand Down
31 changes: 19 additions & 12 deletions Meter.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,19 @@ static inline void Meter_displayBuffer(const Meter* this, RichString* out) {
/* ---------- TextMeterMode ---------- */

static void TextMeterMode_draw(Meter* this, int x, int y, int w) {
if (w <= 0)
return;

const char* caption = Meter_getCaption(this);
attrset(CRT_colors[METER_TEXT]);
mvaddnstr(y, x, caption, w);
attrset(CRT_colors[RESET_COLOR]);

int captionLen = strlen(caption);
x += captionLen;
w -= captionLen;
if (w <= 0)
size_t captionLen = strlen(caption);
if ((size_t)w <= captionLen)
return;
x += (int)captionLen;
w -= (int)captionLen;

RichString_begin(out);
Meter_displayBuffer(this, &out);
Expand Down Expand Up @@ -301,6 +304,10 @@ static void LEDMeterMode_drawDigit(int x, int y, int n) {
}

static void LEDMeterMode_draw(Meter* this, int x, int y, int w) {
assert(x >= 0);
if (w <= 0)
return;

#ifdef HAVE_LIBNCURSESW
if (CRT_utf8)
LEDMeterMode_digits = LEDMeterMode_digitsUtf8;
Expand All @@ -318,25 +325,25 @@ static void LEDMeterMode_draw(Meter* this, int x, int y, int w) {
y + 2;
attrset(CRT_colors[LED_COLOR]);
const char* caption = Meter_getCaption(this);
mvaddstr(yText, x, caption);
int xx = x + strlen(caption);
mvaddnstr(yText, x, caption, w);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is a little beyond my knowledge. According to man pages, the n in mvaddnstr refers to number of "characters" to print. However, what I really want to limit is the number of terminal columns, not the number of Unicode characters (this distinction is needed if East Asian Wide characters are in the strings).

Perhaps a good way is to test how this function call would output, would it be "12" or just "":

/* "1234 fullwidth" */
addnstr("\uFF11\uFF12\uFF13\uFF14 fullwidth", 2);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about wcswidth(3)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I wish is a function that does an inverse of wcswidth(3). wcswidth() returns the terminal column width given a string and a number of (Unicode) characters limit. What I wished is retrieving the number of characters (or number of bytes) given a string and a column width limit. RichString_appendnWideColumns() might give the closest I need, but I want a function that operates on a const char* rather than a RichString object. Maybe I would write one from scratch?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately that is easier said than actual done, but based on the fact that wcswidth returns >=0 you can probably build something like this by calling wcwidth(3) for each character in turn until you overshoot the limit, in which case you know the last character was too much (this accounts for zero-width-combiners and other special cases). The down-side is, that you will need to transform the const char* into wchar_t* first, thus having this functionality live with the other RichString functions might be reasonable.

NB: There's actually an issue that might benefit from this functionality in #462 IIRC.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BenBE Yes, I'm aware of PR #462. And perhaps PR #1231 can benefit, too, as I wish this function would work as a word wrapper that is also CJK-aware.

size_t xx = (size_t)x + strlen(caption);
int len = RichString_sizeVal(out);
for (int i = 0; i < len; i++) {
for (size_t i = 0; i < (size_t)len; i++) {
int c = RichString_getCharVal(out, i);
if (c >= '0' && c <= '9') {
if (xx - x + 4 > w)
if (xx + 4 > (unsigned int)x + (unsigned int)w)
break;

LEDMeterMode_drawDigit(xx, y, c - '0');
LEDMeterMode_drawDigit((int)xx, y, c - '0');
xx += 4;
} else {
if (xx - x + 1 > w)
if (xx + 1 > (unsigned int)x + (unsigned int)w)
break;
#ifdef HAVE_LIBNCURSESW
const cchar_t wc = { .chars = { c, '\0' }, .attr = 0 }; /* use LED_COLOR from attrset() */
mvadd_wch(yText, xx, &wc);
mvadd_wch(yText, (int)xx, &wc);
#else
mvaddch(yText, xx, c);
mvaddch(yText, (int)xx, c);
#endif
xx += 1;
}
Expand Down
12 changes: 6 additions & 6 deletions Row.c
Original file line number Diff line number Diff line change
Expand Up @@ -428,23 +428,23 @@ void Row_printNanoseconds(RichString* str, unsigned long long totalNanoseconds,
}

unsigned long long totalSeconds = totalMicroseconds / 1000000;
unsigned long microseconds = totalMicroseconds % 1000000;
uint32_t microseconds = totalMicroseconds % 1000000;
if (totalSeconds < 60) {
int width = 5;
unsigned long fraction = microseconds / 10;
uint32_t fraction = microseconds / 10;
if (totalSeconds >= 10) {
width--;
fraction /= 10;
}
len = xSnprintf(buffer, sizeof(buffer), "%u.%0*lus ", (unsigned int)totalSeconds, width, fraction);
len = xSnprintf(buffer, sizeof(buffer), "%u.%0*lus ", (unsigned int)totalSeconds, width, (unsigned long)fraction);
RichString_appendnAscii(str, baseColor, buffer, len);
return;
}

if (totalSeconds < 600) {
unsigned int minutes = totalSeconds / 60;
unsigned int seconds = totalSeconds % 60;
unsigned int milliseconds = microseconds / 1000;
unsigned int minutes = (unsigned int)totalSeconds / 60;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the cast here happen after the division?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary because of the (totalSeconds < 600) conditional. Although I argue this is more of a style issue...
(Compiler should catch that (unsigned int)(totalSeconds / 60) can be optimized to ((unsigned int)totalSeconds) / 60, no?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily, as (unsigned int)((1UL<<32) / 60) != (unsigned int)(1UL<<32) / 60.

Won't happen in this case based on the previous check, but the correctness should take priority here.

unsigned int seconds = (unsigned int)totalSeconds % 60;
unsigned int milliseconds = (unsigned int)(microseconds / 1000);
len = xSnprintf(buffer, sizeof(buffer), "%u:%02u.%03u ", minutes, seconds, milliseconds);
RichString_appendnAscii(str, baseColor, buffer, len);
return;
Expand Down
6 changes: 3 additions & 3 deletions Settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ static void Settings_readMeterModes(Settings* this, const char* line, unsigned i
}

static bool Settings_validateMeters(Settings* this) {
const size_t colCount = HeaderLayout_getColumns(this->hLayout);
const unsigned int colCount = HeaderLayout_getColumns(this->hLayout);

bool anyMeter = false;

for (size_t column = 0; column < colCount; column++) {
for (unsigned int column = 0; column < colCount; column++) {
char** names = this->hColumns[column].names;
const MeterModeId* modes = this->hColumns[column].modes;
const size_t len = this->hColumns[column].len;
Expand Down Expand Up @@ -154,7 +154,7 @@ static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount)
}

// Release any previously allocated memory
for (size_t i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
String_freeArray(this->hColumns[i].names);
free(this->hColumns[i].modes);
}
Expand Down
Loading