0

I was exploring when gcc uses branching vs conditional moves, and found some odd results when using bitwise ANDing on bit 0 of a variable. Specifically, if I do:

int main(int argc, char** argv) {
    int y = (2*((~argc)&0x1)) + (1*(argc&0x3)); 
    return y;
}

gcc at -O0 computes argc & 0x1 first, then branches to different basic blocks based on that result. The conversion from arithmetic to branching seems to happen early- dumping the original tree, I get:

;; Function main (null)
;; enabled by -tree-original


{
  return (argc & 1) * 2 + ((argc & 1) == 0 ? 3 : 0); 
}
return 0;

and the GIMPLE is

int main (int argc, char * * argv)
{
  int D.2409;
  int iftmp.0;

  {
    _1 = argc & 1;
    _2 = _1 * 2;
    _3 = argc & 1;
    if (_3 == 0) goto <D.2411>; else goto <D.2412>;
    <D.2411>:
    iftmp.0 = 3;
    goto <D.2413>;
    <D.2412>:
    iftmp.0 = 0;
    <D.2413>:
    D.2409 = iftmp.0 + _2; 
    return D.2409;
  }
  D.2409 = 0;
  return D.2409;
}

This doesn't seem to happen if I don't use both sides of the bitwise AND. It also doesn't seem to happen if I AND with 0x2 instead of 0x1. In those cases, I get purely data processing code, with no jumps. And of course, at optimization levels of 1 or greater, I get optimized jump-free code, but I'm still curious why/how/where GCC converts to branching.

Testing on godbolt, it seems like clang doesn't do this at all, and GCC started doing the conversion to branching between versions 4.1.2 and 4.4.7.

  • Probably it recognizes parts of the expression as some common pattern which is usually translated into branching code. – Eugene Sh. Nov 16 '21 at 20:06
  • You might have better luck asking this question on the mailing list `gcc@gcc.gnu.org`. – zwol Nov 16 '21 at 20:14
  • Also interesting: https://www.godbolt.org/z/b1KEbExcK – Jabberwocky Nov 16 '21 at 20:43
  • I wonder if it's something like that a value that can only be 0 or 1 is treated like a boolean? Even at `-O0`, gcc will still do some transformations in evaluating expressions, since it's presumably inexpensive and doesn't affect debugability, which are the main goals of `-O0`. It's not an utterly naive compiler. Of course, in this case the transformation seems to be a deoptimization. – Nate Eldredge Nov 16 '21 at 23:17
  • My hunch is that yes, it's related to being 0 or 1 and how the front end parser is handling that, but since this happens before any optimization pass that I'm aware of (the "original" graph is as early in the build process as I can get a dump), I'm confused about e.g. what compiler step could be doing it. I didn't think there should be any semantic transformations on the parse tree step, but especially since I only get the branching if I use both 1&argc and 1&~argc, I figure it must be some semantic transformation. – Taylor Phebillo Nov 16 '21 at 23:29

0 Answers0