https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120360

--- Comment #2 from Linus Torvalds <torva...@linux-foundation.org> ---
Btw, for a similar - but different - comparison optimization failure case, we
had a discussion about our error pointer comparisons in the kernel.

We have this model where we return a pointer or error code in the same word,
with error pointers having values from -MAX_ERR to -1. 

So we have a "IS_ERR_OR_NULL()" thing, which is the obvious thing and generates

        testq   %rdi, %rdi
        je      .L189
        cmpq    $-4096, %rdi
        ja      .L189

for gcc, and clang does this:

        testq   %rdi, %rdi
        sete    %al
        cmpq    $-4095, %rdi                    # imm = 0xF001
        setae   %cl
        orb     %al, %cl
        je      .LBB3_1

to avoid multiple conditional branches.

But for the case where we know it's a kernel pointer (which is always a
negative value on x86-64) we could just add MAX_ERR and check that it's now
positive. And by "add MAX_ERR", I mean "subtract -MAX_ERR and turn it into just
a comparison".

But I *cannot* get gcc to do that. I can get gcc to generate this:

        addq    $4095, %rdi
        jns     .L7

which looks superficially fine, but is actually quite bad because it generates
that extra result, which then results in more register pressure because you
need to keep the original pointer around, of course. 

Why doesn't gcc know that an add with a compare and a dead result is the same
as subtract with the negative value, which can be written as "cmp"?

IOW, the code I *tried* to get gcc to generate was

        cmpq    $-4095, %rdi
        jns     .L7

but gcc just refuses to do that.

As with https://gcc.gnu.org/bugzilla/show_bug.cgi?id=49095, I feel like there
should be an obvious peephole optimization where you go "oh, I can turn this
add-with-only-CC to just a cmp".

Stupid test-case:

  /*
   * Compile with -fwrapv, or cast to 'unsigned long' and then to 'long'
   */
  #define MY_MAX_ERRNO 4095
  #define MY_IS_ERR_OR_NULL(ptr) ((long)(ptr) + MY_MAX_ERRNO >= 0)

  extern void do_something(void);
  void failure(void *);

  void failure(void *ptr)
  {
        if (MY_IS_ERR_OR_NULL(ptr)) do_something();
  }

Reply via email to