Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

externally visible lambda in a static data member of a class is given internal linkage and wrong mangled name #58819

Closed
zygoloid opened this issue Nov 4, 2022 · 15 comments
Labels
ABI Application Binary Interface c++17 clang:frontend Language frontend issues, e.g. anything involving "Sema"

Comments

@zygoloid
Copy link
Collaborator

zygoloid commented Nov 4, 2022

Testcase:

struct A {
    static constexpr auto x = [] { static int n; return &n; };
};
int *f() {
    return A::x();
}

A::x() is required to return the same address in every translation unit. But Clang emits the lambda and its static local with internal linkage and with a vendor-specific $_n mangling:

@_ZN1A1xE = internal constant %class.anon undef, align 1, !dbg !0
@"_ZZNK1A3$_0clEvE1n" = internal global i32 0, align 4, !dbg !5
; ...
define internal noundef ptr @"_ZNK1A3$_0clEv"(ptr noundef nonnull align 1 dereferenceable(1) %0) #1 align 2 !dbg !7 {

See this live on compiler explorer.

See also the relevant ABI rule; the case we're in here is "the initializers of inline or templated variables".

@zygoloid zygoloid added c++17 ABI Application Binary Interface labels Nov 4, 2022
@llvmbot
Copy link
Collaborator

llvmbot commented Nov 4, 2022

@llvm/issue-subscribers-clang-codegen

@llvmbot
Copy link
Collaborator

llvmbot commented Nov 4, 2022

@llvm/issue-subscribers-c-17

@dwblaikie
Copy link
Collaborator

First pass at this.

Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, D);
successfully sets the appropriate LambdaDeclContext to be the VarDecl.
But then
PushExpressionEvaluationContext(
happens and sets the lambda decl context to null.

Not sure why the latter doesn't happen for other adjacent cases (like lambdas in default argument initializers in a class, or anywhere else for that matter)... yet to be looked into.

@dwblaikie
Copy link
Collaborator

Hmm, nope, that seems to happen in the good cases too - so that's a slight red herring...

@dwblaikie
Copy link
Collaborator

dwblaikie commented Nov 5, 2022

Ah:

// -- the bodies of non-exported nonspecialized template functions
// -- the bodies of inline functions
if ((IsInNonspecializedTemplate &&
!(ManglingContextDecl && isa<ParmVarDecl>(ManglingContextDecl))) ||
isInInlineFunction(CurContext)) {
while (auto *CD = dyn_cast<CapturedDecl>(DC))
DC = CD->getParent();
return std::make_tuple(&Context.getManglingNumberContext(DC), nullptr);
}
return std::make_tuple(nullptr, nullptr);

Only mentions two out of the four cases listed in the Itanium spec:

* default arguments appearing in class definitions
* default member initializers
* the bodies of inline or templated functions
* the initializers of inline or templated variables

@dwblaikie
Copy link
Collaborator

oh, it lists other cases later/elsewhere in the switch, but I guess doesn't fully/correctly implement the cases..

@dwblaikie
Copy link
Collaborator

Huh, does GCC have a bug here too? I managed to fix the mangling, and I think the linkage too, but the symbols still differ from GCC and it looks like GCC's using internal linkage?

struct A {
  static constexpr auto foo = [] {
    static int bar;
    return &bar;
  };
};
int *f() { return A::foo(); }
$ diff <(clang++-tot lambda_bad.cpp -c && nm lambda_bad.o | c++filt) <(g++-12 lambda_bad.cpp -c && nm lambda_bad.o | c++filt)
1,4c1,4
< 0000000000000000 T f()
< 0000000000000000 V A::foo
< 0000000000000000 W A::{lambda()#1}::operator()() const
< 0000000000000000 V A::{lambda()#1}::operator()() const::bar
---
> 0000000000000011 T f()
> 0000000000000000 r A::foo
> 0000000000000000 t A::{lambda()#1}::operator()() const
> 0000000000000000 b A::{lambda()#1}::operator()() const::bar

@dwblaikie
Copy link
Collaborator

if I'm reading this correctly, that is.

@dwblaikie
Copy link
Collaborator

yeah, I have a small test case that changes behavior under optimizations with GCC (due to inlining) and not with Clang. Guess I could simplify it further and get it to have the wrong behavior even without optimization...

@dwblaikie
Copy link
Collaborator

lambda.h:

struct A {
  static constexpr auto foo = [] {
    static int bar;
    return &bar;
  };
};

a.cpp:

#include "lambda.h"
int *f1() {
  return A::foo();
}

b.cpp:

#include "lambda.h"
int *f1();
int main() {
  return A::foo() != f1();
}

Returns 1 in a non-conforming implementation (GCC and Clang ToT) and 0 when conforming (with a prototype patch I have for clang), I think?

@dwblaikie
Copy link
Collaborator

Should we be using the variable context mangling, though? GCC isn't - it's mangling without the variable name, using the class scope:

But it looks like from the example in the itanium spec, we should use the type name:

template<typename T> struct S {
  static int *x;
};
template<typename T> int *S<T>::x = []{static int x; return &x;}();
template int *S<int>::x;
  // [blaikie: modified the example above to be int* instead of int, 
  //  and the static local, so these mangling's don't quite line up]
  // Type of lambda in intializer of S<int>::x: N1SIiE1xMUlvE_E
  // Corresponding operator(): _ZNK1SIiE1xMUlvE_clEv

Hmm, GCC and Clang both get this ^ case right... oh, wait, no, GCC gets the mangling right (though it differs from GCC's handling of the non-template case) using the 'x' member, but gets the linkage different from Clang at least

$ clang++-tot lambda_spec.cpp -c && nm lambda_spec.o  | c++filt
0000000000000000 t __cxx_global_var_init
0000000000000000 V guard variable for S<int>::x
0000000000000000 V S<int>::x
0000000000000000 W S<int>::x::{lambda()#1}::operator()() const
0000000000000000 V S<int>::x::{lambda()#1}::operator()() const::x
$ g++-12 lambda_spec.cpp -c && nm lambda_spec.o  | c++filt
000000000000004b t _GLOBAL__sub_I_lambda_spec.cpp
0000000000000000 t __static_initialization_and_destruction_0(int, int)
0000000000000000 u guard variable for S<int>::x
0000000000000000 u S<int>::x
0000000000000000 W S<int>::x::{lambda()#1}::operator()() const
0000000000000000 u S<int>::x::{lambda()#1}::operator()() const::x

Are those ABI compatible linkages? (V and u?) Maybe those are fine & don't tell us anything about the broken non-template case... but I think they still point to the name mangling being incorrect in GCC, and that it should be using the variable name as part of the mangling.

@dwblaikie
Copy link
Collaborator

GCC bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107741
(turned out they missed the member variable scope, but also the numbering isn't scoped to the type, despite the name having the type scoping - lambdas in one type were causing the lambda number in later types to be incremented)

@dwblaikie
Copy link
Collaborator

@dwblaikie
Copy link
Collaborator

Fixed in e9c9db3

https://godbolt.org/z/sWo7EYsxP

@EugeneZelenko EugeneZelenko added clang:frontend Language frontend issues, e.g. anything involving "Sema" and removed clang:codegen labels Jun 29, 2023
@llvmbot
Copy link
Collaborator

llvmbot commented Jun 29, 2023

@llvm/issue-subscribers-clang-frontend

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ABI Application Binary Interface c++17 clang:frontend Language frontend issues, e.g. anything involving "Sema"
Projects
None yet
Development

No branches or pull requests

4 participants