https://gcc.gnu.org/bugzilla/show_bug.cgi?id=123565
Bug ID: 123565
Summary: extended asm outside functions vs used/defined
tracking
Product: gcc
Version: 15.2.1
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: other
Assignee: unassigned at gcc dot gnu.org
Reporter: roland at gnu dot org
Target Milestone: ---
This may actually be two separate bugs but they seem intimately related, so I
have both demonstrated in one test case.
Consider the following test case (C++, using `-std=c++20` but that's likely not
very relevant):
```
namespace {
struct C {
static void Public() { AsmDefined(); }
static void Defined() {}
static void AsmDefined();
};
void LocalDefined() {}
void LocalAsmDefined();
__asm__(
R"""(
.pushsection .text.special, "ax", %%progbits
.type %cc[AsmDefined], %%function
%cc[AsmDefined]: ret
.popsection
)"""
:
: [AsmDefined] ":"(C::AsmDefined));
__asm__(
R"""(
.pushsection .data.rel.ro.special, "aw", %%progbits
.dc.a %cc[Defined]
.popsection
)"""
:
: [Defined] "-s"(C::Defined));
__asm__(
R"""(
.pushsection .text.special, "ax", %%progbits
.type %cc[LocalAsmDefined], %%function
%cc[LocalAsmDefined]: ret
.popsection
)"""
:
: [LocalAsmDefined] ":"(LocalAsmDefined));
__asm__(
R"""(
.pushsection .data.rel.ro.special, "aw", %%progbits
.dc.a %cc[LocalDefined]
.popsection
)"""
:
: [LocalDefined] "-s"(LocalDefined));
} // namespace
void Public() {
LocalAsmDefined();
C::Public();
}
```
Compiled as `g++ -S -o - -O3 -std=c++20 -Wall -Wextra gcc-asm-symbols.cc`, this
gets:
```
gcc-asm-symbols.cc:13:6: warning: 'void {anonymous}::LocalAsmDefined()'
declared 'static' but never defined [-Wunused-function]
13 | void LocalAsmDefined();
| ^~~~~~~~~~~~~~~
gcc-asm-symbols.cc:8:15: warning: 'static void {anonymous}::C::AsmDefined()'
used but never defined
8 | static void AsmDefined();
| ^~~~~~~~~~
gcc-asm-symbols.cc:13:6: warning: 'void {anonymous}::LocalAsmDefined()' used
but never defined
13 | void LocalAsmDefined();
| ^~~~~~~~~~~~~~~
```
The asm statements are emitted correctly, using mangled symbol names for local
symbols. The `LocalAsmDefined` and `C::AsmDefined` functions are indeed
defined validly in assembly by the code emitted, but the compiler wrongly
claims that they are undefined.
(Incidentally, it seems inconsistent that the "declared 'static' but never
defined" warning is not emitted for the class (static) member function that
also has `static`-equivalent internal linkage by dint of being in an internal
linkage class. It's wrong that I'm getting that warning about the
namespace-scope function, but in cases where that warning is correct, it should
apply equally to class-scoped functions that wind up with internal linkage for
whatever reason unless the linkage is template-dependent or whatnot.)
What's more shocking is that the `LocalDefined` and `C::Defined` references are
emitted correctly into assembly, but referring to local symbols that the
compiler chooses mangled names for and then never defines. The asm statements
"use" the symbols enough to render them correctly as operands, but then the
compiler claims they were not used!
I'm using the 15 branch built at revision
90c718dec540095c4eab0bfbde60d3d09d7e2012, but I think this behavior has been
there since the advent of the new extended asm outside functions and ":"
constraint features (added in 15).
I've observed the same behavior on aarch64-elf, x86_64-elf, and riscv64-elf
targets. I suspect the bugs are not target-specific at all. My test case is
only specific to ELF-based targets that have an instruction called `ret` and is
easily adjusted for other targets (though actually I've just been eyeballing
the .s output rather than assembling it, so you could do that with this
verbatim test case for another target too).
This usage seems pretty close to the manual's example for what the whole
purpose of the ":" constraint is, and this was just introduced in 15 and touted
in NEWS as a new feature. So it seems pretty problematic that it seems like it
can't be used in the only scenario where it would really matter. (Why else did
it matter for the compiler to have ":" as the "symbol-defining" constraint
distinct from something like "-s", if not to have the compiler notice the
definition? And where else does the compiler noticing the definition matter
except for an internal linkage definition, or perhaps LTO?)
The wrongly-elided (and wrongly warned about as unused) function cases can be
mitigating with `[[gnu::used]]`, but that certainly shouldn't be needed when
the symbols are obviously used in the source as asm operands.
The wrongly-claimed-to-be-undefined function cases can only be worked around by
giving these apparent external linkage names. If the asm statements defining
them don't make them global symbols at the assembly / ELF level, then that
won't necessarily pollute the actual program linkage name space. But it's a
pretty bad wart when the ":" constraint exists exactly for this purpose.