Re: git: af3c78886fd8 - main - Alter the prototype of qsort_r(3) to match POSIX, which adopted the glibc-based interface.

From: Xin Li <delphij_at_FreeBSD.org>
Date: Sun, 09 Oct 2022 01:24:32 UTC

On 10/8/22 12:07, Alexey Dokuchaev wrote:
> On Sat, Oct 08, 2022 at 01:32:57PM +0000, Pedro Giffuni wrote:
>> Complain here:
>> https://www.austingroupbugs.net/view.php?id=900
> 
> Thanks, the first comment there more or less answers my question.

Well, it probably did not.

The FreeBSD implementation approach is mostly a template based 
implementation, so in the library qsort, qsort_r, historical qsort_r and 
qsort_s are different and independent routines, which are instantiated 
from the same template at compile time.  Therefore, there is no such 
thing of performance penalty as they suggested.

The only downside of this approach is that you get multiple copies of 
mostly identical code so the library could potentially be a little bit 
larger, this, however, is not really a big deal on modern hardware, 
because the actual size of these routines is small (~9kB).

And by paying this price, we avoided all costs associated with their 
passing of third parameter at runtime for qsort() users.  Indeed, it 
doesn't make a big difference to pass an extra register, which can well 
be reused for multiple calls, but having different comparator types is a 
clean way to do things.

In fact, if there is no need to use a third (or first) parameter for the 
comparator, there is absolutely no point of using qsort_r() because now 
the caller is passing three parameter per each comparison instead of 
two, and if the code wasn't reentrant-safe, it won't be automatically 
become reentrant-safe by using qsort_r().

Now they say, it's making it easier to implement qsort(3) by using 
qsort_r(3).  It might be if they don't make use of macros, but by doing 
so it would require casting a function pointer to another with different 
parameter type.  Now this work _today_, but it's explicitly defined as 
UB in C standard, let's quote N1548, ยง6.3.2.3:

     A pointer to a function of one type may be converted to a
     pointer to a function of another type and back again; the
     result shall compare equal to the original pointer. If a
     converted pointer is used to call a function whose type is
     not compatible with the referenced type, the behavior is
     undefined.

So they are basically suggesting that:

a) passing an unused parameter via register has almost no cost.

Yes this is mostly true, the compiler would save and restore the 
register if it's ever touched in the function anyway, and for caller the 
contents of the register can be reused during the iteration.

b) it is Okay to rely on UB in the C library

Well, they want to do this, good luck with that.  Not in my backyard.

By doing it their way, the cost would be:

a) qsort(3) callers are penalized for all cost related to their 
qsort_r(3).  It would be very small, but non-zero.

b) Our historical qsort_r(3) API uses first parameter to accept the 
"context", which is a widely used calling convention in other languages. 
  In fact, this is exactly why our qsort_b(3) is implemented as a 
wrapper around qsort_r(3) (now as a wrapper of the historical interface).

The motivation of adopting their API is not because it's superior, but 
because it has been used by many applications already and is now 
standardized.

Cheers,