Creating an alias to a static function in C++

I’m not sure if this is a potential bug report (I could file something more formally) or what, but I’m hitting something that’s pretty weird to me.

G++ (random versions from GCC 4.6ish to 7.3) accepts the following code:

extern “C” {
static attribute((used)) void f1() {}
void f() attribute ((weak, alias(“f1”)));
}

however Clang rejects it (interested in version 6 but earlier versions reject as well), with

:3:35: error: alias must point to a defined variable or function

(sandbox: https://godbolt.org/g/38VVPS)

After playing around it for a bit, I notice something interesting. If I drop the alias and static, I get an object file with the expected symbols:

$ cat huh.cc
extern “C” {
attribute((used)) void f(int x) { }
}
$ clang -c huh.cc && nm huh.o
0000000000000000 T f

but if I include the static, then I get something that is weird to me:

$ cat huh.cc
extern “C” {
static attribute((used)) void dummy1(int x) { }
}
$ clang -c huh.cc && nm huh.o
0000000000000000 t f
0000000000000000 t _ZL1fi
$ clang -c huh.cc && nm huh.o | c++filt
0000000000000000 t f
0000000000000000 t f(int)

So I tried using the mangled name in the alias, and that works:

extern “C” {
static attribute((used)) void f1 () {}
void f () attribute ((weak, alias(“_ZL2f1v”))); // OK
}

and of course then so does this

static attribute((used)) void f1 () {}
extern “C” {
void f () attribute ((weak, alias(“_ZL2f1v”)));
}

Furthermore, if I browse the Itanium ABI page, I can’t even figure out how you get a “_ZL…” mangled symbol, but I’m sure I’m missing something (e.g. c++filt demangles it fine…)

Anyway, I can easily work around this is my actual code, but how much of this is expected behavior?

Evan

  $ cat huh.cc
  extern "C" {
    static __attribute__((used)) void dummy1(int x) { }
  }
  $ clang -c huh.cc && nm huh.o
  0000000000000000 t f
  0000000000000000 t _ZL1fi
  $ clang -c huh.cc && nm huh.o | c++filt
  0000000000000000 t f
  0000000000000000 t f(int)

In the interest of hopefully eliminating confusion -- sorry for the output
not exactly matching up with the examples in terms of function names. I was
working half in Compiler Explorer and half with a file on a local file
system. I noticed as I was finishing the email, and thought I'd try to
align the names by editing the command output to match... but of course
that is always a bad idea. :slight_smile:

Anyway, of course that code doesn't produce an object file with a symbol
'f' in it, it'd be 'dummy1'. The presence of both the unmangled and mangled
symbols is the same though.

Evan

  $ cat huh.cc
  extern "C" {
    static __attribute__((used)) void dummy1(int x) { }
  }
  $ clang -c huh.cc && nm huh.o
  0000000000000000 t f
  0000000000000000 t _ZL1fi
  $ clang -c huh.cc && nm huh.o | c++filt
  0000000000000000 t f
  0000000000000000 t f(int)

In the interest of hopefully eliminating confusion -- sorry for the output
not exactly matching up with the examples in terms of function names. I was
working half in Compiler Explorer and half with a file on a local file
system. I noticed as I was finishing the email, and thought I'd try to
align the names by editing the command output to match... but of course
that is always a bad idea. :slight_smile:

Anyway, of course that code doesn't produce an object file with a symbol
'f' in it, it'd be 'dummy1'. The presence of both the unmangled and mangled
symbols is the same though.

In C++, extern "C" means two different things:

1) All[*] function types within the region are given C language linkage
(which means they use the C calling convention). In GCC and Clang, the C
calling convention is the same as the C++ calling convention, so this
doesn't really matter (and actually isn't implemented at all, which is
slightly non-conforming).
2) All[*] function and variable declarations *with external linkage* within
the region are given C language linkage (which means they use the C symbol
names).

Point 2 is the relevant one here, and the key fact is that it only applies
to functions with external linkage. In particular, this is valid:

extern "C" {
  static void f(int) {}
  static void f(double) {}
}

... and the two functions do not collide per the standard C++ rules.

However, GCC historically did not implement this rule that way (and as far
as I know, still doesn't), and applies the C language linkage rules even to
internal linkage functions.

Clang chooses to try to satisfy both the goal of C++ standard conformance
and GCC compatibility, and the way it does that is to use two different
mangled names for such internal-linkage-in-extern-C blocks: a C++ one
(allowing overloading), and a C alias; the latter is omitted in the case
where it would collide with another symbol (which effectively means we try
to emit it only after we emit everything else).

I would guess the problem is that we check that the referent of
__attribute__((alias(...))) exists before we register the static extern "C"
aliases, which is why the lookup is failing.

[*]: Irrelevant exceptions elided

Evan

So I tried using the mangled name in the alias, and that works:

  extern "C" {
    static __attribute__((used)) void f1 () {}
    void f () __attribute__ ((weak, alias("_ZL2f1v"))); // OK
  }

and of course then so does this

  static __attribute__((used)) void f1 () {}
  extern "C" {
    void f () __attribute__ ((weak, alias("_ZL2f1v")));
  }

Furthermore, if I browse the Itanium ABI page, I can't even figure out
how you get a "_ZL..." mangled symbol, but I'm sure I'm missing something
(e.g. c++filt demangles it fine...)

_ZL is a GCC mangling extension used to mangle the names of
internal-linkage symbols. Because it's only used for internal-linkage
symbols, it's not part of the ABI per se, but it's still useful for us to
use the same scheme so that demanglers can produce proper output for our
names. (Historically, C++ allowed some weird constructs where you could
have two distinct symbols, one with internal linkage and one with external
linkage, that would mangle the same, named in the same translation unit, so
a disambiguator was needed. Such cases are now ill-formed, but the legacy
of that remains in our mangling scheme.)

Anyway, I can easily work around this is my actual code, but how much of

this is expected behavior?

Everything except the actual error is expected behavior. For GCC
compatibility, I think we should try to accept examples such as yours.
Interested in putting together a patch?