Hello,

I've proposed a patch [1] for condition coverage profiling in gcc, implemented in the middle-end alongside the branch coverage. I've written most of the tests for C and a few for C++ and finally got around to try it with a toy example for D and go and noticed something odd about Go's CFG construction.

abc.c:
    int fn (int a, int b, int c) {
        if (a && (b || c))
            return a;
        else
            return b * c;
    }

abc.d:
    int fn (int a, int b, int c) {
        if (a && (b || c))
            return a;
        else
            return b * c;
    }

abc.go:
    func fn (a int, b int, c int) int {
        a_ := a != 0;
        b_ := b != 0;
        c_ := c != 0;

        if a_ && (b_ || c_) {
            return 1;
        } else {
            return 0;
        }
    }

All were built with gcc --coverage -fprofile-conditions (my patch, but it does not affect this) and no optimization. The C and D programs behaved as expected:

gcov --conditions abc.d:

        3:    3:int fn (int a, int b, int c) {
       3*:    4:    if (a && (b || c))
conditions outcomes covered 3/6
condition  1 not covered (false)
condition  2 not covered (true)
condition  2 not covered (false)
        1:    5:        return a;
        -:    6:    else
        2:    7:        return b * c;


gcov --conditions abc.go:
        3:    5:func fn (a int, b int, c int) int {
        3:    6:        a_ := a != 0;
        3:    7:        b_ := b != 0;
        3:    8:        c_ := c != 0;
        -:    9:
       3*:   10:        if a_ && (b_ || c_) {
condition outcomes covered 2/2
condition outcomes covered 1/2
condition  0 not covered (true)
condition outcomes covered 2/2
        1:   11:            return 1;
        -:   12:        } else {
        2:   13:            return 0;
        -:   14:        }
        -:   15:}

So I dumped the gimple gcc -fdump-tree-gimple abc.go:

int main.fn (int a, int b, int c)
{
  int D.2725;
  int $ret0;

  $ret0 = 0;
  {
    bool a_;
    bool b_;
    bool c_;

    a_ = a != 0;
    b_ = b != 0;
    c_ = c != 0;
    {
      {
        GOTMP.0 = a_;
        if (GOTMP.0 != 0) goto <D.2719>; else goto <D.2720>;
        <D.2719>:
        {
          {
            GOTMP.1 = b_;
            _1 = ~GOTMP.1;
            if (_1 != 0) goto <D.2721>; else goto <D.2722>;
            <D.2721>:
            {
              GOTMP.1 = c_;
            }
            <D.2722>:
          }
          GOTMP.2 = GOTMP.1;
          GOTMP.0 = GOTMP.2;
        }
        <D.2720>:
      }
      if (GOTMP.0 != 0) goto <D.2723>; else goto <D.2724>;
<D.2723>:


      {
        {
          $ret0 = 1;
          D.2725 = $ret0;
          // predicted unlikely by early return (on trees) predictor.
          return D.2725;
        }
      }
      <D.2724>:
      {
        {
          $ret0 = 0;
          D.2725 = $ret0;
          // predicted unlikely by early return (on trees) predictor.
          return D.2725;
        }
      }
    }
  }
}

Where as D (and C) is more-or-less as you would expect:

int fn (int a, int b, int c)


{
  int D.7895;

  if (a != 0) goto <D.7893>; else goto <D.7891>;
  <D.7893>:
  if (b != 0) goto <D.7892>; else goto <D.7894>;
  <D.7894>:
  if (c != 0) goto <D.7892>; else goto <D.7891>;
  <D.7892>:
  D.7895 = a;
  // predicted unlikely by early return (on trees) predictor.
  return D.7895;
  <D.7891>:
  D.7895 = b * c;
  // predicted unlikely by early return (on trees) predictor.
  return D.7895;
}

Clearly the decision inference algorithm is unable to properly instrument to Go program for condition coverage because of the use of temporaries in the emitted GIMPLE. The question is: is this intentional and/or required from Go's semantics or could it be considered a defect? Is emitting the GIMPLE without the use of temporaries feasible at all?

Thanks,
Jørgen

[1] https://gcc.gnu.org/pipermail/gcc-patches/2022-July/598165.html

Reply via email to