This is an archive of the discontinued LLVM Phabricator instance.

[clang][Interp] Create only globals when initializing a global variable
ClosedPublic

Authored by tbaeder on Jul 27 2023, 9:31 AM.

Details

Summary

For this code:

struct O {
  int &&j;
};

O o1(0);

The generated AST for the initializer of o1 is:

VarDecl 0x62100006ab08 <array.cpp:119:3, col:9> col:5 o1 'O':'O' parenlistinit
`-ExprWithCleanups 0x62100006b250 <col:7, col:9> 'O':'O'
  `-CXXParenListInitExpr 0x62100006b210 <col:7, col:9> 'O':'O'
    `-MaterializeTemporaryExpr 0x62100006b1f0 <col:8> 'int' xvalue
      `-IntegerLiteral 0x62100006abd0 <col:8> 'int' 0

Before this patch, we create a local temporary variable for the MaterializeTemporaryExpr and destroy it again when destroying the EvalEmitter we create to interpret the initializer. However, since O::j is a reference, this reference now points to a local variable that doesn't exist anymore.

I've run into this issue a few times before but always postponed working on it. Does this make sense? The only downside I see is that we might be creating more global variables.

Diff Detail

Event Timeline

tbaeder created this revision.Jul 27 2023, 9:31 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 27 2023, 9:31 AM
tbaeder requested review of this revision.Jul 27 2023, 9:31 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 27 2023, 9:31 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript

I guess I'm a bit lost as to what the code example has to do with the constant expression interpreter in the first place.

It gets interpreted as a constant expression in Sema::CheckCompleteVariableInitialization():

* #0: Context.cpp:73  clang::interp::Context::evaluateAsInitializer(this=0x0000608000005d20, Parent=0x00007fff6dc2a8d0, VD=0x0000621000073b48, Result=0x0000621000099e90)
  #1: ExprConstant.cpp:15600  clang::Expr::EvaluateAsInitializer(this=0x0000621000099e40, Value=0x0000621000099e90, Ctx=0x000062a000000200, VD=0x0000621000073b48, Notes=0x00007fff6deac850, IsConstantInitialization=true) const
  #2: Decl.cpp:2555  clang::VarDecl::evaluateValueImpl(this=0x0000621000073b48, Notes=0x00007fff6deac850, IsConstantInitialization=true) const
  #3: Decl.cpp:2626  clang::VarDecl::checkForConstantInitialization(this=0x0000621000073b48, Notes=0x00007fff6deac850) const
  #4: SemaDecl.cpp:14399  clang::Sema::CheckCompleteVariableDeclaration(this=0x000062900000a200, var=0x0000621000073b48)

It gets interpreted as a constant expression in Sema::CheckCompleteVariableInitialization():

* #0: Context.cpp:73  clang::interp::Context::evaluateAsInitializer(this=0x0000608000005d20, Parent=0x00007fff6dc2a8d0, VD=0x0000621000073b48, Result=0x0000621000099e90)
  #1: ExprConstant.cpp:15600  clang::Expr::EvaluateAsInitializer(this=0x0000621000099e40, Value=0x0000621000099e90, Ctx=0x000062a000000200, VD=0x0000621000073b48, Notes=0x00007fff6deac850, IsConstantInitialization=true) const
  #2: Decl.cpp:2555  clang::VarDecl::evaluateValueImpl(this=0x0000621000073b48, Notes=0x00007fff6deac850, IsConstantInitialization=true) const
  #3: Decl.cpp:2626  clang::VarDecl::checkForConstantInitialization(this=0x0000621000073b48, Notes=0x00007fff6deac850) const
  #4: SemaDecl.cpp:14399  clang::Sema::CheckCompleteVariableDeclaration(this=0x000062900000a200, var=0x0000621000073b48)

Ahh, we are doing this for static initialization cases with thread locals (https://godbolt.org/z/73dMx55xq), okay, that makes more sense now. Thank you!

clang/lib/AST/Interp/ByteCodeExprGen.cpp
1210–1211

Should we be looking at the TLS kind of the extended declaration? (CheckCompleteVariableDeclaration() is using VarDecl::getTLSKind() == VarDecl::TLS_Static)

Would something along these lines work instead?

bool EmitGlobalTemp = E->getStorageDuration() == SD_Static;
if (!EmitGlobalTemp) {
  if (const LifetimeExtendedTemporaryDecl *LETD = E->getLifetimeExtendedTemporaryDecl()) {
    if (const auto *VD = dyn_cast_if_present<VarDecl>(LETD->getExtendingDecl()) {
      EmitGlobalTemp= VD->getTLSKind() == VarDecl::TLS_Static;
    }
  }
}
clang/test/AST/Interp/records.cpp
1074

It'd be nice to add another test for:

constinit O o2(0); // error: can't bind to temporary
tbaeder updated this revision to Diff 557713.Oct 15 2023, 11:32 PM
tbaeder marked an inline comment as done.
tbaeder added inline comments.
clang/lib/AST/Interp/ByteCodeExprGen.cpp
1210–1211

That code definitely works for the current AST/Interp/ tests, but we don't have tests for thread local stuff in there right now.

aaron.ballman added inline comments.Oct 16 2023, 11:58 AM
clang/lib/AST/Interp/ByteCodeExprGen.cpp
1210–1211

Hmm, I think we'll need those tests: https://eel.is/c++draft/expr.const#5.2

That seems to be the only mention about thread local storage duration for constant expressions in C++, so it might make sense to tackle that as part of this change?

(I worry that we'll forget to come back to this detail later, basically. So either we should have failing test coverage showing we need a fix, an issue in GitHub so we know to come back to it, etc. or just do the work up front given that it's closely related.)

tbaeder added inline comments.Oct 17 2023, 1:40 AM
clang/lib/AST/Interp/ByteCodeExprGen.cpp
1210–1211

I've pushed https://github.com/llvm/llvm-project/commit/11f5e5eb90c883d4b9ddba318e8fc57914b22ef3

As you can see, the problem also exists for static variables. I think this needs additional checks at interpretation time, or even new opcodes to check the declaration when the initializer is computed. So I'd rather have this in a separate followup patch.

aaron.ballman accepted this revision.Oct 17 2023, 6:08 AM

LGTM

clang/lib/AST/Interp/ByteCodeExprGen.cpp
1210–1211

Follow-up patch is fine, thank you for the additional test coverage for it!

This revision is now accepted and ready to land.Oct 17 2023, 6:08 AM