CCing Go maintainer. Martin
On 8/3/22 15:25, j wrote: > > 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