This is an archive of the discontinued LLVM Phabricator instance.

Emit static constexpr member as available_externally definition
ClosedPublic

Authored by mehdi_amini on Jul 4 2017, 3:33 PM.

Event Timeline

mehdi_amini created this revision.Jul 4 2017, 3:33 PM

Does this apply to all constexpr global variables? It could potentially fix https://bugs.llvm.org/show_bug.cgi?id=31860 .

mehdi_amini added inline comments.Jul 5 2017, 11:48 AM
clang/test/CodeGenCXX/cxx11-extern-constexpr.cpp
13

Note: I'm not sure if the standard allows us to assume that we can fold the value for BAR2 without seeing the definition here?
(so we could be even more aggressive)

16

Looks like I have a bug here, this should be an internal.

ahatanak added inline comments.
clang/lib/CodeGen/CodeGenModule.cpp
2375

Does getAnyInitializer ever return a null pointer here when D is a c++11 constexpr?

mehdi_amini added inline comments.Jul 5 2017, 4:14 PM
clang/lib/CodeGen/CodeGenModule.cpp
2375

That's a good question, I wouldn't expect so. I can try adding an assertion instead.
I guess @rsmith could confirm as well.

rsmith edited edge metadata.Jul 5 2017, 4:25 PM

We've had problems in the past with speculative emission of values like this resulting in us producing invalid symbol references. (Two cases I remember: an available_externally symbol references a symbol that should be emitted as linkonce_odr but which is not emitted in this TU, and an available_externally symbol references a symbol with hidden visibility that is actually defined in a different DSO. In both cases, if the value of the available_externally symbol is actually used, you end up with a program with an invalid symbol reference.)

I don't immediately see any ways that this change would be susceptible to such a problem, so perhaps our best bet is to try it and see if it actually breaks anything.

clang/lib/CodeGen/CodeGenModule.cpp
2373

Applying this for only constexpr variables seems unnecessarily conservative; it seems we could equally do this for any variable that is const and has no mutable fields (though we'd need to check for EmitConstantInit failing in the general case).

2375

Only in error cases, which shouldn't get this far.

clang/test/CodeGenCXX/cxx11-extern-constexpr.cpp
2

Please also test what happens in C++1z mode, particularly as static constexpr data members are implicitly inline there.

16

I would imagine that we skip promotion of declaration to definition in this case if we already have a definition.

To that end, please add a testcase like this:

struct Bar {
  static constexpr int BAZ = 42;
};
auto *use = &Bar::BAZ;
const int Bar::BAZ;

... to make sure that we convert the definition of Bar::BAZ from available_externally to a strong definition (I think we should end up with weak_odr here).

mehdi_amini added inline comments.Jul 5 2017, 4:36 PM
clang/lib/CodeGen/CodeGenModule.cpp
2373

OK I will improve, and include a test with a struct with a mutable field.

clang/test/CodeGenCXX/cxx11-extern-constexpr.cpp
2

This is already covered by `clang/test/CodeGenCXX/cxx1z-inline-variables.cpp ` (I actually broke this test during development because I didn't know about inline variable). But I can also add coverage for it here if you'd like.

16

weak_odr in C++17 because it is an inline variable, but I expect a strong definition in c++11.
I'll add this, this is a good test-case!

rsmith added inline comments.Jul 5 2017, 4:42 PM
clang/test/CodeGenCXX/cxx11-extern-constexpr.cpp
16

Well, weak_odr is a kind of strong definition :)

Ah, I'd intended to change from emitting this as a strong definition to emitting it as weak_odr even in C++11 (for better forward-compatibility with C++17), but it looks like I never did so. So yes, we'd expect an ExternalLinkage global here (for now) in C++11.

Fix issues around mutable fields and regression on "internal", add more testing.

@rsmith: post-C++-commitee-meeting ping ;)

Ping again @rsmith (or anyone else)

rsmith added inline comments.Aug 3 2017, 5:11 PM
clang/lib/CodeGen/CodeGenModule.cpp
2293

This comment change suggests that when ExcludeCtor is true, a trivial constructor is considered. That's the opposite of what this change does -- namely, a trivial constructor is not considered regardless of the value of ExcludeCtor.

2302

This change looks wrong: you don't know that the default constructor would be used to initialize the object in question, so whether the default constructor is trivial seems like it should have no bearing on the result.

I think you can change the code below that uses GV->isConstant() to call isTypeConstant instead, and remove the need for any changes here.

2433

Typo "it's" -> "its"

2435

Instead of GV->isConstant(), it would make more sense to use isTypeConstant(D->getType(), true) here.

2437–2445

This duplicates much of the work done by isTypeConstant.

2448–2451

In order for this transformation to be correct, you need to know that the variable has static initialization, which means that it needs to formally have a constant initializer. You can use D->isInitKnownICE() to check this.

mehdi_amini marked 12 inline comments as done.

Address Richard's comment

mehdi_amini marked 6 inline comments as done.Aug 4 2017, 2:18 AM
mehdi_amini added inline comments.
clang/lib/CodeGen/CodeGenModule.cpp
2293

Yep your right. I removed all the changes here.

2437–2445

Indeed, thanks for pointing this, this simplifies a lot!

2448–2451

Done! But couldn't find how to express it as a test-case though.

mehdi_amini marked an inline comment as done.Aug 17 2017, 8:29 AM

Bi-weekly ping! (@rsmith)

FYI, this doesn't compiler after John's constant emitter refactoring (r310964)

rsmith accepted this revision.Aug 18 2017, 10:57 AM

I'd like to also see a testcase for the situation where we trigger the emission of a declaration with an available_externally definition and then find we need to promote it to a "full" definition:

struct A {
  static const int Foo = 123;
};
int *p = &A::Foo; // emit available_externally
const int A::Foo; // convert to full definition
clang/lib/CodeGen/CodeGenModule.cpp
2437

Do we need to special-case this? Such declarations are definitions.

2448–2451

You'd need to use an initializer that we can constant-fold, such as:

struct A {
  static const int n;
};
bool check() {
  assert(A::n == 0 && "already initialized!");
  return true;
}
const int A::n = (check() || true) ? 1 : 2;
This revision is now accepted and ready to land.Aug 18 2017, 10:57 AM
This revision was automatically updated to reflect the committed changes.

I'd like to also see a testcase for the situation where we trigger the emission of a declaration with an available_externally definition and then find we need to promote it to a "full" definition:

Added!

clang/lib/CodeGen/CodeGenModule.cpp
2437

We don't! Simplified after rebasing and adapting after John's changes.

2448–2451

But this wouldn't be an "available_externally". We need the initializer in the class definition.

I tried multiple variant but couldn't get one that compile:

struct B {
  static bool check() {
    if (B::n == 0) return false;
    return true;
  }
  // error: in-class initializer for static data member is not a constant expression
  static const int n = (B::check() || true) ? 1 : 2;
};
struct B {
  static constexpr bool check() {
// "error: constexpr function never produces a constant expression"
    return  (B::n == 0 || true) ? false : true;
  }
//"note: initializer of 'n' is not a constant expression"
  static constexpr int n = (B::check() || true) ? 1 : 2;
};