This is an archive of the discontinued LLVM Phabricator instance.

Emit const globals with constexpr destructor as constant LLVM values
ClosedPublic

Authored by hans on Mar 6 2023, 4:53 AM.

Details

Summary

This follows 2b4fa53 which made Clang not emit destructor calls for such objects. However, they would still not get emitted as constants since CodeGenModule::isTypeConstant() only checked whether the destructor was trivial, not if it's constexpr.

Fixes Issue #61212

Diff Detail

Event Timeline

hans created this revision.Mar 6 2023, 4:53 AM
Herald added a project: Restricted Project. · View Herald TranscriptMar 6 2023, 4:53 AM
hans requested review of this revision.Mar 6 2023, 4:53 AM
Herald added a project: Restricted Project. · View Herald TranscriptMar 6 2023, 4:53 AM

+aaron.ballman

I don't have a lot of experience in codegen, so will let Aaron and Richard do the review.

However, still wanted to share one observation. The actual check that avoids emitting the destructors for variables seems more involved than just checking if the destructor is constexpr.
Is it safe to rely solely on the type for codegen? E.g. I would have expected checks that look at HasConstantDestruction for variables with non-trivial constructors.

hans added a comment.Mar 9 2023, 6:20 AM

I don't have a lot of experience in codegen, so will let Aaron and Richard do the review.

Thanks for checking! I don't have a lot of experience here either, so any review is much appreciated.

However, still wanted to share one observation. The actual check that avoids emitting the destructors for variables seems more involved than just checking if the destructor is constexpr.
Is it safe to rely solely on the type for codegen? E.g. I would have expected checks that look at HasConstantDestruction for variables with non-trivial constructors.

Right, codegen is also factoring in the check about emitting destructors when deciding about marking the LLVM value constant or not:

in CodeGenModule::EmitGlobalVarDefinition()

  bool NeedsGlobalDtor =
      !IsDefinitionAvailableExternally &&
      D->needsDestruction(getContext()) == QualType::DK_cxx_destructor;

...

  // If it is safe to mark the global 'constant', do so now.
  GV->setConstant(!NeedsGlobalCtor && !NeedsGlobalDtor &&
                  isTypeConstant(D->getType(), true));

So I *think* this is correct, and that the hasConstexprDestructor() case was just missing from isTypeConstant() because constexpr destructors were added later. But again, not super familiar with this stuff :)

This seems reasonable to me, but adding the codegen code owners in case they disagree.

There appear to be 8 different places that call isTypeConstant(Ty, true); I'd like to see testcases for all of them if it's relevant. (I agree with your assessment for global variables, but not sure about the other places.)

hans updated this revision to Diff 505127.Mar 14 2023, 8:58 AM

Thanks for the review!

Added tests to cover other calls that change behavior due to this.

Please take another look.

efriedma added inline comments.Mar 14 2023, 11:43 AM
clang/test/CodeGenCXX/static-init.cpp
181

I don't see how this works. For a static local variable, in general, we register the destructor with __cxa_atexit, and the destructor can have arbitrary side-effects. For example:

extern void foo();
struct A {
  constexpr A() : x(1) {}
  constexpr ~A() {if (x) foo();}
  int x;
};
const int *f() {
  static const A a{};
  return &a.x;
}
efriedma added inline comments.Mar 14 2023, 11:54 AM
clang/lib/CodeGen/CodeGenModule.cpp
4349

For the purposes of CodeGen, checking Record->hasConstexprDestructor() isn't really helpful. Even if Record->hasConstexprDestructor() is true, a destructor can still have side-effects. Since the callers need to handle that possibility anyway, you might as well just skip the Record->hasTrivialDestructor() || Record->hasConstexprDestructor() check.

hans marked an inline comment as done.Mar 15 2023, 6:20 AM
hans added inline comments.
clang/lib/CodeGen/CodeGenModule.cpp
4349

Maybe what we want is to also have an ExcludeDtor param that callers which have checked that no dtor call is needed could set?

clang/test/CodeGenCXX/static-init.cpp
181

Hmm, I guess I assumed the destructor being constexpr meant it wouldn't have side effects, which of course isn't necessarily true.. thanks for the example.

hans updated this revision to Diff 505466.Mar 15 2023, 6:20 AM
hans marked an inline comment as done.

Passing a ExcludeDtor param.

efriedma accepted this revision.Mar 15 2023, 4:09 PM

LGTM

clang/lib/CodeGen/CGExprAgg.cpp
535–536

I'm not sure why we're querying isTypeConstant here? The global shouldn't get written to in any case. But I guess that's a separate issue.

This revision is now accepted and ready to land.Mar 15 2023, 4:09 PM
This revision was automatically updated to reflect the committed changes.
hans marked an inline comment as done.
hans added inline comments.Mar 16 2023, 3:30 AM
clang/lib/CodeGen/CGExprAgg.cpp
535–536

Yeah, it seems safe to always make the global const. Doing that here: https://reviews.llvm.org/D146211