Using both OpenCL and GCC vectors

Hi,

In our testing, we discovered a regression in 3.8 when trying to use both OpenCL and GCC vectors in the same program. Consider the following code:

template

static void foo() {}

void bar() {

foo<float attribute((vector_size(16)))>();

foo<float attribute((ext_vector_type(4)))>();

}

In 3.7, the code compiled without any errors, but in 3.8 it fails to compile. The problem is that clang considers GCC and OpenCL vectors to be unique types, but since they mangle identically, the compiler instantiates two versions of foo which are identical and emits an error:

repro.cpp:2:13: error: definition with same mangled name as another definition

static void foo() {}

^

repro4.cpp:2:13: note: previous definition is here

1 error generated.

The error is unfortunately not very helpful and was filed as PR25343. However, we feel that the compiler could be smarter in these cases. For example, the compiler could recognize that it is generating the same function when instantiating the second instance of foo and reuse the first one instead of creating another one. This would prevent the creation of two identically mangled names which caused the error seen above. Or perhaps the compiler could be smarter and when it encounters the second instantiation of foo, it would issue an error at that point with a useful diagnostic.

Thoughts?

Douglas Yung

I guess another option would be to mangle differently OpenCL and GCC vectors. Not sure what would be better though…

Cheers,

Anastasia

Hi Douglas,

There's a big difference between acting on unambiguous scenarios and
"being smart". Compilers are generally safer if they don't do the
latter.

For example, the compiler could recognize that it is generating the same
function when instantiating the second instance of foo and reuse the first
one instead of creating another one. This would prevent the creation of two
identically mangled names which caused the error seen above.

Why is this case more special than any other mangling conflict that
should correctly be refused?

What if I created a class that behaves *exactly* like a double and had:

foo<double>() {}
foo<MyDouble>() {}

would you expect the compiler to know what you mean?

Or perhaps the
compiler could be smarter and when it encounters the second instantiation of
foo, it would issue an error at that point with a useful diagnostic.

That's a much better approach, but "being smart" again, is easy for
you to assess on your particular case, but harder for the compiler to
do the right thing (tm) on every case.

For me, it seems that this is just a case of also printing the origin
of the declaration when related to templates, since the user never
instantiates those functions anyway.

cheers,
--renato

Hi Renato,

There's a big difference between acting on unambiguous scenarios and
"being smart". Compilers are generally safer if they don't do the
latter.

I agree with you here.

> For example, the compiler could recognize that it is generating the
> same function when instantiating the second instance of foo and reuse
> the first one instead of creating another one. This would prevent the
> creation of two identically mangled names which caused the error seen
above.

Why is this case more special than any other mangling conflict that
should correctly be refused?

What if I created a class that behaves *exactly* like a double and had:

foo<double>() {}
foo<MyDouble>() {}

would you expect the compiler to know what you mean?

The main difference between your example and the one I gave was that both of the types in my original example are types provided by the compiler, and not something that the user created. Because of this issue, a user could create a program which fails to compile using only types provided by the compiler.

For example, consider what would happen if the int and float types had the same mangling. Would it be proper that the compiler would now reject the following code even though float and double are unique types?

foo<int>() {}
foo<double>() {}

I would argue it is not okay, and that is what is happening here with the two vector types.

> Or perhaps the
> compiler could be smarter and when it encounters the second
> instantiation of foo, it would issue an error at that point with a
useful diagnostic.

That's a much better approach, but "being smart" again, is easy for you
to assess on your particular case, but harder for the compiler to do
the right thing (tm) on every case.

Again I agree, but was just referencing this specific vector case when I was suggesting the compiler could be smarter.

Douglas Yung

The main difference between your example and the one I gave was that both of the types in my original example are types provided by the compiler, and not something that the user created. Because of this issue, a user could create a program which fails to compile using only types provided by the compiler.

The dangers of treating compiler-specific types differently than user
types is that the compiler types can change with time, lose the
similarity and either you now generate broken code, or code that your
users are not expecting.

That was my point about "being smart".

A "smart" compiler can deduce special properties of its internal
implementations, and as long as the users know what that means, the
code is as expected. Once the meaning changes, the compiler is still
being "smart", but in an unexpected way, because the users don't know
it changed, and then for the class of users where the behaviour
changes, the compiler is now "dumb", while for the rest it's still
"smart". Surely you can see that this is a bad situation to be put in.

For example, consider what would happen if the int and float types had the same mangling. Would it be proper that the compiler would now reject the following code even though float and double are unique types?

Absolutely, yes! The error here is about the final symbol, and if you
can't tell them apart, then why, you have an ambiguity. It's as simple
as that. That's why the standard spends so much space defining
mangling, it IS important to get it right, otherwise linkers wouldn't
know what to do. You don't want to push your "smart" solution all the
way down the linker.

If the types are different, but mangle in the same way, the correct
behaviour here is to either:

1. Mangle them differently, but that will break ABI compatibility and
you'll end up with template implementation explosion of identical
functions.

2. Error out with re-defined symbol, but in a way to identify the
source of the error. (Clang errs here). The user then changes the
source to use the same compiler type.

Again I agree, but was just referencing this specific vector case when I was suggesting the compiler could be smarter.

Smart is always relative and personal. Compilers need to be correct
and unambiguous.

cheers,
--renato

Hi,

If the types are different, but mangle in the same way, the correct
behaviour here is to either:

1. Mangle them differently, but that will break ABI compatibility and
you'll end up with template implementation explosion of identical
functions.

2. Error out with re-defined symbol, but in a way to identify the
source of the error. (Clang errs here). The user then changes the
source to use the same compiler type.

What worries us here is that code which previously compiled successfully (although likely incorrectly) on 3.7 is now failing to compile with 3.8. The compiler currently implements the second behavior your described, but gives a slightly unhelpful error message that could be improved (PR25343).

I feel that the first behavior is better in the long run as these are two unique types. However, if either does decide to change, it will not be a quick process, and this issue exists today in a compiler that is about to be released. Because of that, I think it would be helpful to users if we added a note in the clang vector documentation that these two vectors cannot be used together in some situations due to this issue. What do you think?

Douglas Yung