1

Here's a minimal, reproducible example:

#include <tuple>

int main()
{
  std::pair<int, int> pair {1, 2};
  int sum = 0;
#pragma omp parallel for
  for (int i = 1; i < 10; i++)
  {
      auto [a, b] = pair;
#pragma omp critical 
      sum += a + b;
  }
}

Now, g++ (10.2.0) compiles clean

g++ 1.cpp -std=c++17 -fopenmp -pedantic

Replacing g++ with clang++ (11.0.0) results in:

> clang++ 1.cpp -std=c++17 -fopenmp -pedantic
1.cpp:12:14: error: reference to local binding 'a' declared in enclosing context
      sum += a + b;
             ^
1.cpp:10:13: note: 'a' declared here
      auto [a, b] = pair;
            ^
1.cpp:12:18: error: reference to local binding 'b' declared in enclosing context
      sum += a + b;
                 ^
1.cpp:10:16: note: 'b' declared here
      auto [a, b] = pair;
               ^
2 errors generated.

Now comes a real surprise. In my Linux (Manjaro) I need to link clang++ with libgomp to use OpenMP,

clang++ 1.cpp -std=c++17 -fopenmp=libgomp -pedantic

and now the program compiles with no error, no warning.

I found it in a larger program written in QtCreator. The problem is that now QtCreator shows a red bullet to warn me against an error, but the program compiles without warning (g++).

Two questions:

  1. Why clang behaves so strangely? Why does its parser appear to depend on external libraries, like libgomp?
  2. Is there a way to silence the error warning in QtCreator without disabling the "error: reference to local binding 'XXX' declared in enclosing context" altogether, other then by rewriting the code?

See also (similar question, but with lambdas instead OpenMP): Lambda implicit capture fails with variable declared from structured binding

EDIT: Full output of clang with -v:

> clang++ 1.cpp -fopenmp  -std=c++17 -v -pedantic
clang version 11.0.0
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-pc-linux-gnu/10.2.0
Found candidate GCC installation: /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.2.0
Found candidate GCC installation: /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0
Found candidate GCC installation: /usr/lib64/gcc/x86_64-pc-linux-gnu/10.2.0
Selected GCC installation: /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.2.0
Candidate multilib: .;@m64
Candidate multilib: 32;@m32
Selected multilib: .;@m64
 "/usr/bin/clang-11" -cc1 -triple x86_64-pc-linux-gnu -emit-obj -mrelax-all -disable-free -disable-llvm-verifier -discard-value-names -main-file-name 1.cpp -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=all -fmath-errno -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -fno-split-dwarf-inlining -debugger-tuning=gdb -v -resource-dir /usr/lib/clang/11.0.0 -internal-isystem /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../include/c++/10.2.0 -internal-isystem /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../include/c++/10.2.0/x86_64-pc-linux-gnu -internal-isystem /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../include/c++/10.2.0/backward -internal-isystem /usr/local/include -internal-isystem /usr/lib/clang/11.0.0/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -pedantic -std=c++17 -fdeprecated-macro -fdebug-compilation-dir /home/zkoza/so -ferror-limit 19 -fopenmp -fopenmp-cuda-parallel-target-regions -stack-protector 2 -fgnuc-version=4.2.1 -fcxx-exceptions -fexceptions -fcolor-diagnostics -faddrsig -o /tmp/1-45b8f4.o -x c++ 1.cpp
clang -cc1 version 11.0.0 based upon LLVM 11.0.0 default target x86_64-pc-linux-gnu
ignoring nonexistent directory "/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../include/c++/10.2.0
 /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../include/c++/10.2.0/x86_64-pc-linux-gnu
 /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../include/c++/10.2.0/backward
 /usr/local/include
 /usr/lib/clang/11.0.0/include
 /usr/include
End of search list.
1.cpp:12:14: error: reference to local binding 'a' declared in enclosing context
      sum += a + b;
             ^
1.cpp:10:13: note: 'a' declared here
      auto [a, b] = pair;
            ^
1.cpp:12:18: error: reference to local binding 'b' declared in enclosing context
      sum += a + b;
                 ^
1.cpp:10:16: note: 'b' declared here
      auto [a, b] = pair;
               ^
2 errors generated.

EDIT 2: The error disappears if I comment out the critical section. Apparently clang 11's module controlling program names is a bit in conflict with a similar module that must be implemented for OpenMP (to decide which variables inside parallel regions are thread-local and which are shared).

zkoza
  • 2,644
  • 3
  • 16
  • 24
  • Please add a -v to the clang++ command so we can see what is actually being run. It's not completely clear which clang you have, since when I try this (admittedly with a mainline clang, so 12.0 but not quite yet :-)) the -fopenmp flag doesn't accept an argument... (and does compile your code with no complaint). – Jim Cownie Jan 21 '21 at 14:10
  • @JimCownie I've added the output of clang + `-v` – zkoza Jan 21 '21 at 20:19
  • Do you not still have a data race for the definitions of the restructured variables, in your min. reproducible example? – Mansoor Jan 21 '21 at 20:24
  • @M.A What do you mean by "data race"? The program does not compile with `-fopenmp`. – zkoza Jan 21 '21 at 20:27
  • @zkoza Two threads can sequentially overwrite variables a & b, then in any order, they can accumulate (the same value) into sum. – Mansoor Jan 21 '21 at 20:29
  • @M.A `a` and `b` are local to the for-loop scope and thus local to each thread. Modification of shared `sum` is protected by a critical section. Gcc compiles this code, clang - doesn't. – zkoza Jan 21 '21 at 22:40
  • @M.A It suffices to replace the structured binding with `int a, b; std::tie(a,b) = pair;` to make clang quite happy. – zkoza Jan 21 '21 at 22:42
  • @zkoza `a and b are local to the for-loop scope and thus local to each thread`, this statement is not valid, otherwise, thread-local semantics would not be necessary. Also, note that code compiling does not preclude the presence of race conditions or data races. I'm am not familiar with OpenMP syntax, so just speculating. Assuming that you have multi-thread inside the for-loop before the critical section, then you do have a race condition. The only reason the result is the same for this example is that each thread uses the same value, but in your real code, you should be wary of this. – Mansoor Jan 21 '21 at 23:06
  • Does it help to use `const std::pair`? I think clang might choke on the fact that `pair` is not thread-local and mutable and therefore refuses the structured binding which binds references under the hood (although so does `std::tie`, so maybe that's wrong). – Henri Menke Jan 23 '21 at 13:10
  • @HenriMenke No, this changes nothing. However, if I remove the critical section pragma, my clang becomes very happy and issues no compiler warning. – zkoza Jan 23 '21 at 21:05
  • 1
    Related: https://stackoverflow.com/q/46114214/580083 – Daniel Langr Jan 25 '21 at 09:40
  • I still have a similar issue in Clang 14. https://godbolt.org/z/1a1GG5jdq – Amin Ya Jul 04 '22 at 20:32

1 Answers1

3

I cannot answer why clang 11.0.1 does not like this code, though one can see the effect in Compiler Explorer (https://godbolt.org/z/qTTcGf.

However, one can also see there why adding the perverse flag which you think is enabling use of libgomp makes the code compile. The reason is that that flag is disabling OpenMP completely. You can see that there are no calls to OpenMP runtime routines in https://godbolt.org/z/o3TaGz . I have no idea why that flag is not rejected, since it makes absolutely no sense to ask Clang to link against libgomp, as Clang cannot generate the calls into the GCC OpenMP RTL (these interfaces are different). It is also not documented as a reasonable thing to do (see https://clang.llvm.org/docs/UsersManual.html#openmp-features ) which does not show -fopenmp taking any argument.

You can also see (at https://godbolt.org/z/7hcdrr) that the mainline Clang now accepts your code (without disabling OpenMP) and generates code that includes calls into the OpenMP runtime.

So, overall

  1. Clang 11 has a bug which has been fixed in mainline.
  2. You are passing an unsupported argument to clang and it is then turning off OpenMP compilation.
Jim Cownie
  • 2,409
  • 1
  • 11
  • 20
  • I still have a similar issue in Clang 14. https://godbolt.org/z/1a1GG5jdq – Amin Ya Jul 04 '22 at 20:33
  • 1
    @Amin Did you submit a bug report to clang? If not, then do not be surprised that things don't get fixed. https://llvm.org/docs/HowToSubmitABug.html – Jim Cownie Jul 06 '22 at 07:51
  • 1
    Just submitted an issue: https://github.com/llvm/llvm-project/issues/56406 – Amin Ya Jul 06 '22 at 08:21