Bug 100825 - function signature constraints are not a part of mangled name
Summary: function signature constraints are not a part of mangled name
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 11.1.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: ABI, wrong-code
: 82467 101719 (view as bug list)
Depends on:
Blocks:
 
Reported: 2021-05-29 11:54 UTC by vopl
Modified: 2022-05-04 20:40 UTC (History)
8 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description vopl 2021-05-29 11:54:25 UTC
$ cat b.cpp && echo EOFFFFFF

template <class T> void foo() // 1
{}

template <class T> void foo() requires (sizeof(char) == sizeof(T)) // 2
{
    foo<int>();// call 1 - instantiate "void foo<int>()"
}

template <class T> void foo() requires (sizeof(int) == sizeof(T)) // 3
{}

void use()
{
    foo<char>(); // call 2
    foo<int>(); // call 3 - instantiate "void foo<int>() requires (sizeof(int) == sizeof(T))"
}

// so, two different function instantiated with same mangled name

EOFFFFFF

$ g++ -std=c++20 -v -c b.cpp 
Using built-in specs.
COLLECT_GCC=g++
Target: x86_64-pc-linux-gnu
Configured with: /var/tmp/portage/sys-devel/gcc-10.2.0-r5/work/gcc-10.2.0/configure --host=x86_64-pc-linux-gnu --build=x86_64-pc-linux-gnu --prefix=/usr --bindir=/usr/x86_64-pc-linux-gnu/gcc-bin/10.2.0 --includedir=/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/include --datadir=/usr/share/gcc-data/x86_64-pc-linux-gnu/10.2.0 --mandir=/usr/share/gcc-data/x86_64-pc-linux-gnu/10.2.0/man --infodir=/usr/share/gcc-data/x86_64-pc-linux-gnu/10.2.0/info --with-gxx-include-dir=/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/include/g++-v10 --with-python-dir=/share/gcc-data/x86_64-pc-linux-gnu/10.2.0/python --enable-languages=c,c++,fortran --enable-obsolete --enable-secureplt --disable-werror --with-system-zlib --disable-nls --enable-checking=release --with-bugurl=https://bugs.gentoo.org/ --with-pkgversion='Gentoo 10.2.0-r5 p6' --disable-esp --enable-libstdcxx-time --with-build-config=bootstrap-lto --enable-shared --enable-threads=posix --enable-__cxa_atexit --enable-clocale=gnu --enable-multilib --with-multilib-list=m32,m64 --disable-fixed-point --enable-targets=all --enable-libgomp --disable-libssp --disable-libada --enable-systemtap --enable-vtable-verify --with-zstd --enable-lto --with-isl --disable-isl-version-check --enable-default-pie --disable-default-ssp
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 10.2.0 (Gentoo 10.2.0-r5 p6) 
COLLECT_GCC_OPTIONS='-std=c++2a' '-v' '-c' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
 /usr/libexec/gcc/x86_64-pc-linux-gnu/10.2.0/cc1plus -quiet -v -D_GNU_SOURCE b.cpp -quiet -dumpbase b.cpp -mtune=generic -march=x86-64 -auxbase b -std=c++2a -version -o /tmp/ccdHBZw0.s
GNU C++17 (Gentoo 10.2.0-r5 p6) version 10.2.0 (x86_64-pc-linux-gnu)
	compiled by GNU C version 10.2.0, GMP version 6.2.1, MPFR version 4.1.0, MPC version 1.2.1, isl version isl-0.23-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/include/g++-v10
 /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/include/g++-v10/x86_64-pc-linux-gnu
 /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/include/g++-v10/backward
 /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/include
 /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/include-fixed
 /usr/include
End of search list.
GNU C++17 (Gentoo 10.2.0-r5 p6) version 10.2.0 (x86_64-pc-linux-gnu)
	compiled by GNU C version 10.2.0, GMP version 6.2.1, MPFR version 4.1.0, MPC version 1.2.1, isl version isl-0.23-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 109a96b688365221cca69113b161e683
COLLECT_GCC_OPTIONS='-std=c++2a' '-v' '-c' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/as -v --64 -o b.o /tmp/ccdHBZw0.s
GNU assembler version 2.35.2 (x86_64-pc-linux-gnu) using BFD version (Gentoo 2.35.2 p1) 2.35.2
/tmp/ccdHBZw0.s: Assembler messages:
/tmp/ccdHBZw0.s:61: Error: symbol `_Z3fooIiEvv' is already defined



---------------
My expectations: mangled names must be different, because mangled name is based on the signature and a signature includes the requires-clause as a part
Comment 1 vopl 2021-05-29 12:22:43 UTC
a little more minimized:

template <class> void foo() {} //1
void useFirst()
{
    foo<int>();// call 1 - instantiate "void foo<int>()"
}

template <class> void foo() requires true {} //2
void useSecond()
{
    foo<int>();// call 2 - instantiate "void foo<int>() requires true"
}

$ g++ -std=c++20 -c b.cpp 
/tmp/ccnRtmjt.s: Assembler messages:
/tmp/ccnRtmjt.s:59: Error: symbol `_Z3fooIiEvv' is already defined
Comment 3 Jonathan Wakely 2021-06-01 09:47:25 UTC
Clang and EDG agree with GCC here.

I think your code is ill-formed due to [temp.constr.atomic] p3:

"If, at different points in the program, the satisfaction result is different for identical atomic constraints and template arguments, the program is ill-formed, no diagnostic required."
Comment 4 vopl 2021-06-01 10:44:18 UTC
(In reply to Jonathan Wakely from comment #3)
> Clang and EDG agree with GCC here.
> 
> I think your code is ill-formed due to [temp.constr.atomic] p3:
> 
> "If, at different points in the program, the satisfaction result is
> different for identical atomic constraints and template arguments, the
> program is ill-formed, no diagnostic required."

Please, take a look at [temp.constr.atomic] p2: Two atomic constraints, e1 and e2, are identical if they are formed from the same appearance of the same
expression and...

My code contains two functions (last case, from Comment#1):
template <class T> void foo() /*empty constraints*/;
template <class T> void foo() requires true;

empty constraints from first functions is not identical "requires true" from second one, so, [temp.constr.atomic] p3 is not applicable here.

------------
Here is a sample with constraints in both functions:

template <class T> concept C1 = sizeof(T) > 1;
template <class T> concept C2 = C1<T> && sizeof(T) < 24;

template <class T> void foo() requires C1<T> {}
void useFirst()
{
    foo<int>();
}

template <class T> void foo() requires C2<T> {}
void useSecond()
{
    foo<int>();
}

/tmp/ccZLqFRh.s:69: Error: symbol `_Z3fooIiEvv' is already defined

Thanks.
Comment 5 Jonathan Wakely 2021-06-01 11:30:24 UTC
Yes, I realise that, but I think that is the same rule that means you can't change the result of overload resolution for a given call, which is why the second definition gets emitted using the same symbol name as the first.

If the constrained overload is declared before the first call to foo<int>() then there is no error.
Comment 6 vopl 2021-06-01 13:41:44 UTC
(In reply to Jonathan Wakely from comment #5)
> Yes, I realise that, but I think that is the same rule that means you can't
> change the result of overload resolution for a given call, 

But I have a precedent:

void foo(char) {}
void useFirst()
{
    foo(0); // "void foo(char)" used, no "void foo(int)" visible at now
}

void foo(int) {} // introduce second function
void useSecond()
{
    foo(0); // "void foo(int)" selected as more suitable
}



> which is why the
> second definition gets emitted using the same symbol name as the first.

[defns.signature.templ] states that trailing require-clause is a part of function signature, so these are two different functions:
template <class T> void foo() {}
template <class T> void foo() requires true {}

Since the signature is the basis for name mangling - different names are expected for different functions..



> If the constrained overload is declared before the first call to foo<int>()
> then there is no error.

Aha, in such situation there is an only call, no second one, so, no second symbol and no conflicts.



Thanks.
Comment 7 TC 2021-06-02 16:26:34 UTC
I think the code is valid; it's just that the ABI doesn't have a mangling for constraints yet: https://github.com/itanium-cxx-abi/cxx-abi/issues/24
Comment 8 Nickolay Merkin 2021-06-09 16:56:35 UTC
(In reply to Jonathan Wakely from comment #3)
> Clang and EDG agree with GCC here.
> 
> I think your code is ill-formed due to [temp.constr.atomic] p3:
> 
> "If, at different points in the program, the satisfaction result is
> different for identical atomic constraints and template arguments, the
> program is ill-formed, no diagnostic required."

Of course the constraints are different! First constraint is empty, second is always-true.
So, these are different overloads.

Okay, let's help the compiler giving different mangled names:

https://gcc.godbolt.org/z/K8d9vv8oT

namespace a {}
namespace b {}

using namespace a;
using namespace b;

namespace a {
template<class T> void f() { std::cout << __PRETTY_FUNCTION__ << std::endl; }
}

void g() { f<int>(); }

namespace b {
template<class T> void f() requires true { std::cout << __PRETTY_FUNCTION__ << std::endl; }
}

void h() { f<int>(); }

g addresses to a::f, h addresses to b::f.

Is this still "ill-formed, no diagnostics required"?
Does it mean that a compiler may produce any corrupted binary code with any undefined behavior? Just because we wrote same "f<int>()" both times?
I believe, not, it does not.

The program is well-formed.
Both overloads are valid. And both are different, - it is not an ODR violation.

So, the issue is on the compiler's side: wrong rules of mangling.
Comment 9 Jonathan Wakely 2021-06-09 17:25:21 UTC
As comment 7 already said.
Comment 10 Jonathan Wakely 2021-08-02 09:02:09 UTC
*** Bug 82467 has been marked as a duplicate of this bug. ***
Comment 11 Jonathan Wakely 2021-08-02 09:29:30 UTC
*** Bug 101719 has been marked as a duplicate of this bug. ***
Comment 12 Jonathan Wakely 2022-05-04 20:39:49 UTC
At the very least, GCC should give better errors instead of just letting the assembler complain. Clang tells you where the conflicting definitions come from, e.g. for the code in comment 1:

1.C:7:23: error: definition with same mangled name '_Z3fooIiEvv' as another definition
template <class> void foo() requires true {} //2
                      ^
1.C:1:23: note: previous definition is here
template <class> void foo() {} //1
                      ^
1 error generated.


Similarly with EDG:

eccp: diagnostics generated from compilation of 1.int.c:
1.C:7:65: error: redefinition of ‘_Z3fooIiEvv’
    7 | template <class> void foo() requires true {} //2
      |                                                                 ^          
1.C:1:65: note: previous definition of ‘_Z3fooIiEvv’ with type ‘void(void)’
    1 | template <class> void foo() {} //1
      |                                                                 ^          
eccp: end of diagnostics from compilation of 1.int.c
eccp: gcc compilation of 1.int.c returned an exit status of 1


These are both much better than the result with GCC:

/tmp/ccnRtmjt.s: Assembler messages:
/tmp/ccnRtmjt.s:59: Error: symbol `_Z3fooIiEvv' is already defined