Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenMp Task: can't pass argument by reference

g++ -fopenmp main.cpp complains about undefined reference to std::vector. How to fix this?

I have installed the libomp-dev package on Ubuntu.

main.cpp

#include<vector>
#include<iostream>

template<typename T, typename A>
T recursiveSumBody(std::vector<T, A> &vec) {
    T sum = 0;
    #pragma omp task shared(sum)
    {
        sum = recursiveSumBody(vec);
    }
    return vec[0];
}

int main() {
    std::vector<int> a;
    recursiveSumBody(a);
    return 0;
}

Undefined References

/tmp/ccTDECNm.o: In function `int recursiveSumBody<int, std::allocator<int> >(std::vector<int, std::allocator<int> >&) [clone ._omp_cpyfn.1]':
main.cpp:(.text+0x148): undefined reference to `std::vector<int, std::allocator<int> >::vector(std::vector<int, std::allocator<int> > const&)'
collect2: error: ld returned 1 exit status
like image 613
hamster on wheels Avatar asked May 10 '17 18:05

hamster on wheels


2 Answers

To fix the issue, you can manually specify shared(sum, vec) (strongly assuming you want it shared).

Interestingly enough older gcc versions (e.g. 5.4.0) give a much more helpful error message:

error: 'vec' implicitly determined as 'firstprivate' has reference type

Whereas the Intel compiler icpc 17.0.1 gives an "internal error : 0_1855".

Manually specifying firstprivate or private - which makes little sense in your case - results in other more descriptive errors. Note, as Hristo Iliev explained in the other comments, firstprivate would mean that a copy of the vector is made for each thread.

As per the current (4.5) standard:

In an orphaned task generating construct, if no default clause is present, formal arguments passed by reference are firstprivate.

I suppose that applies here. Further,

A variable that appears in a firstprivate clause must not have an incomplete C/C++ type or be a reference to an incomplete type. If a list item in a firstprivate clause on a worksharing construct has a reference type then it must bind to the same object for all threads of the team.

It doesn't appear in a clause, but I think this is still what the standard means.

Now I don't think that std::vector<T, A> is an incomplete type within the template, unless I am missing something about how templates are instantiated. So I do think your code should be valid and given that each thread just binds to the same object, it actually would make sense.

So I do think this is a bug in recent gcc versions as well as the Intel compiler. It looks like the compiler fails to instantiate some things for the template.

Further, adding:

if (0) std::vector<T, A> wtf = vec;

at the beginning of the function makes the code compile and link with gcc. But if firstprivate is added manually, gcc continues to complain that 'vec' has incomplete type.

P.S.: Allowing reference types in data sharing attribute clauses was added in OpenMP 4.5, this is the old gcc gives a different error.

like image 145
Zulan Avatar answered Sep 28 '22 22:09

Zulan


This looks like a bug in GCC, which fails to generate a copy constructor for std::vector<int, std::allocator<int> >. Note that the error comes from the linker and does not occur during the compilation phase. The copy constructor is used in the copy function that initialises the firstprivate parameters of the outlined task function. Forcing the compiler to generate it, e.g. changing

std::vector<int> a;

to

std::vector<int> a, b(a);

fixes the problem.

Here is a more elaborate description. GCC transforms the following code

#pragma omp task shared(sum)
{
    sum = recursiveSumBody(vec);
}

into something like:

struct omp_data_a data_o;

data_o.vec = vec;
data_o.sum = &sum;
GOMP_task(omp_fn_0, &data_o, omp_cpyfn_1, 32, 8, 1, 0, 0, 0);

// --- outlined task body ---
void omp_fn_0(struct omp_data_s & restrict data_i)
{
   struct vector & vec = &data_i->vec;
   *data_i->sum = recursiveSumBody<int>(vec);
   std::vector<int>::~vector(vec);
}

// --- task firstprivate initialisation function ---
void omp_cpyfn_1(struct omp_data_s *data_o, struct omp_data_a *data_i)
{
   data_o->sum = data_i->sum;
   struct vector &d40788 = data_i->vec;
   struct vector *this = &data_o->vec;
   std::vector<int>::vector(this, d40788); // <--- invocation of the copy constructor
}

omp_cpyfn_1 gets called by GOMP_task() in order to initialise the firstprivate arguments. It calls the copy constructor of std::vector<int>, because (first-)private treats references to type T as type T itself, but the constructor is not generated, therefore the object code fails to link. This is probably a bug in the gimplifier code as the copy constructor gets created when a non-reference std::vector<T, A> gets privatised, e.g., with code like this:

...
std::vector<T, A> b;
#pragma omp task shared(sum)
{
    sum = recursiveSumBody(b);
}
...

The code compiles with Intel 18.0b. Explicitly specifying vec as firstprivate breaks it the same way as with GCC (icpc complains about vec being of an incomplete type). The following workaround could be used:

template<typename T, typename A>
T recursiveSumBody(std::vector<T, A> &vec) {
    T sum = 0;
    std::vector<T, A> *ptr = &vec;
    #pragma omp task shared(sum)
    {
        sum = recursiveSumBody(*ptr);
    }
    return vec[0];
}

In this case ptr is a pointer. Its firstprivate version is another pointer that points to the same location, i.e. the vector instance. The semantics differs from the original code as here no private copy of the entire vector gets created, rather the original vector is used.

like image 42
Hristo Iliev Avatar answered Sep 28 '22 21:09

Hristo Iliev