This is an archive of the discontinued LLVM Phabricator instance.

[Clang] Fix consteval propagation for aggregates and defaulted constructors
ClosedPublic

Authored by cor3ntin on Jul 13 2023, 4:05 AM.

Details

Summary

This patch does a few things:

  • Fix aggregate initialization. When an aggregate has an initializer that is immediate-escalating, the context in which it is used automatically becomes an immediate function. The wording does that by rpretending an aggregate initialization is itself an invocation which is not really how clang works, so my previous attempt was... wrong.
  • Fix diagnostics In some cases clang would produce additional and unhelpful diagnostics by listing the invalid references to consteval function that appear in immediate escalating functions

Fixes https://github.com/llvm/llvm-project/issues/63742

Diff Detail

Event Timeline

cor3ntin created this revision.Jul 13 2023, 4:05 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 13 2023, 4:05 AM
cor3ntin requested review of this revision.Jul 13 2023, 4:05 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 13 2023, 4:05 AM
cor3ntin added reviewers: aaron.ballman, Restricted Project.Jul 13 2023, 4:06 AM
cor3ntin added a reviewer: Fznamznon.

Do we need CodeGen testcases here for full coverage? The testcases from the issue passed with -fsyntax-only.


With this patch, the following cases produce errors that don't really make sense to me:

consteval int f(int x) {
    return x;
}
struct SS {
    int y;
    int x = f(this->y);
    constexpr SS(int yy) : y(yy) {}
};
SS s = {1};
<stdin>:6:13: error: call to consteval function 'f' is not a constant expression
    6 |     int x = f(this->y);
      |             ^
<stdin>:7:15: note: in the default initializer of 'x'
    7 |     constexpr SS(int yy) : y(yy) {}
      |               ^
<stdin>:6:5: note: declared here
    6 |     int x = f(this->y);
      |     ^
<stdin>:6:15: note: use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function
    6 |     int x = f(this->y);
      |               ^
1 error generated.
consteval int f(int x) {
    return x;
}
struct SS {
    int y;
    int x = f(this->y);
    consteval SS(int yy) : y(yy) {}
};
SS s = {1};
<stdin>:6:13: error: cannot take address of consteval function 'f' outside of an immediate invocation
    6 |     int x = f(this->y);
      |             ^
<stdin>:1:15: note: declared here
    1 | consteval int f(int x) {
      |               ^
1 error generated.

Somehow the following is accepted, though:

consteval int f(int x) {
    return x;
}
struct SS {
    int y;
    int x = f(this->y);
    template<typename T> constexpr SS(T yy) : y(yy) {}
};
SS s = {1};
cor3ntin added a comment.EditedJul 14 2023, 6:17 AM

Do we need CodeGen testcases here for full coverage? The testcases from the issue passed with -fsyntax-only.


With this patch, the following cases produce errors that don't really make sense to me:

consteval int f(int x) {
    return x;
}
struct SS {
    int y;
    int x = f(this->y);
    constexpr SS(int yy) : y(yy) {}
};
SS s = {1};
<stdin>:6:13: error: call to consteval function 'f' is not a constant expression
    6 |     int x = f(this->y);
      |             ^
<stdin>:7:15: note: in the default initializer of 'x'
    7 |     constexpr SS(int yy) : y(yy) {}
      |               ^
<stdin>:6:5: note: declared here
    6 |     int x = f(this->y);
      |     ^
<stdin>:6:15: note: use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function
    6 |     int x = f(this->y);
      |               ^
1 error generated.

This seems reasonable, it's equivalent to constexpr SS(int yy) : y(yy), x(f(y)) {} = which is not a valid call of a consteval function. And the constructor is neither defaulted, nor templated, so escalation does not happen.
I agree that "use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function" isn't amazing but it's preexisting
https://godbolt.org/z/sa78bzsed

consteval int f(int x) {
    return x;
}
struct SS {
    int y;
    int x = f(this->y);
    consteval SS(int yy) : y(yy) {}
};
SS s = {1};
<stdin>:6:13: error: cannot take address of consteval function 'f' outside of an immediate invocation
    6 |     int x = f(this->y);
      |             ^
<stdin>:1:15: note: declared here
    1 | consteval int f(int x) {
      |               ^
1 error generated.

Yup, this is not great - I think it's unrelated the degradation of error message was introduced between 15 and 16, I probably want to investigate in a separate patch, not sure yet.

Scratch that, this is a bug, see below

Somehow the following is accepted, though:

consteval int f(int x) {
    return x;
}
struct SS {
    int y;
    int x = f(this->y);
    template<typename T> constexpr SS(T yy) : y(yy) {}
};
SS s = {1};

Yes, this is bad. Thanks for finding it.
It compiles because 1 is a constant and somehow it ends up evaluating f(1) instead of f(y) - replacing 1 by a reference to a local variable produce the expected behavior. Investigating.

Scratch that previous comment, this is fine, that code is correct
SS::SS is immediate escalating because it is templated and SS::SS(1) is a valid constant expression.

There is however a separate bugs that in the non template case we do not evaluate
default members in an immediate context.
That is easy enough to fix!

I found further issues with aggregates,
I think not supporting aggregates for now would be fine, the high bit is to fix the regressions
I did add some more info there https://github.com/cplusplus/CWG/issues/354#issuecomment-1636129255

cor3ntin updated this revision to Diff 540773.Jul 16 2023, 3:12 AM

Fix the evaluation context of member initializers

Do we need CodeGen testcases here for full coverage? The testcases from the issue passed with -fsyntax-only.

You have something specific in mind?
The examples in the issues now lead to sema errors.

cor3ntin updated this revision to Diff 540809.Jul 16 2023, 7:38 AM

Assert if we try to generate code for a CallExpr to an immediate function

You have something specific in mind?

I was thinking something along the lines of the original testcase in 63742, which successfully compiled in 16, started producing an assertion on main, and successfully compiles with this patch.

cor3ntin updated this revision to Diff 540902.Jul 17 2023, 1:29 AM

Add more tests for aggregates and a codegen test

Herald added a project: Restricted Project. · View Herald TranscriptJul 17 2023, 1:29 AM

Test changes look fine. (I'm not really comfortable reviewing the Sema logic.)

cor3ntin updated this revision to Diff 541057.Jul 17 2023, 8:32 AM

Revert inadvertently committed cmake changes.

Just to clarify, this was a regression introduced during Clang 17 development and thus this doesn't need a release note?

clang/include/clang/Sema/Sema.h
6588
clang/lib/Sema/SemaDeclCXX.cpp
2442–2443
clang/lib/Sema/SemaExpr.cpp
6222

Under what circumstances can we have a FieldDecl without a parent object? I think this can be true, right?

18439
clang/test/CodeGenCXX/cxx2c-consteval-consteval.cpp
1–2 ↗(On Diff #541057)
clang/test/SemaCXX/cxx2a-consteval-default-params.cpp
58

Helps make it more obvious this is a continuation of the previous comment.

clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
201–202
219
240
245–246

I don't have a suggestion yet on how to improve this, but this diagnostic did catch me by surprise because nothing about the declaration of SS s; makes me think "this should be a valid constant expression", so the error felt misplaced.

Fznamznon added inline comments.Jul 19 2023, 1:47 AM
clang/lib/Sema/SemaDeclCXX.cpp
2442–2443

Is there any logical value in passing FunctionScopeInfo instead of a bool flag?

clang/lib/Sema/SemaExpr.cpp
6221

Could you please describe why this change is required?

18467

I would prefer spelling type here.

cor3ntin marked 7 inline comments as done.Jul 19 2023, 2:02 AM
cor3ntin added inline comments.
clang/lib/Sema/SemaDeclCXX.cpp
2442–2443

FYI FunctionScopeInfo is declared in sema and removing that does not seem possible.

2442–2443

in ~SynthesizedFunctionScope() we do not have a definition for it. the alternative would be to move SynthesizedFunctionScope to Sema.cpp

clang/lib/Sema/SemaExpr.cpp
6221

Sure, if we have an immediate invocation that refers to the this pointer it might need to be transformed, and that transformation will call getCurrentThisType which needs this to point to the right type

18467

that's an std::optional<ExpressionEvaluationContextRecord::InitializationContext>, I'm not sure spelling it improved readability. But i can change it!

Fznamznon added inline comments.Jul 19 2023, 2:37 AM
clang/lib/Sema/SemaExpr.cpp
18467

Uh, yeah, doesn't seem to be improving readability. NVM :)

cor3ntin updated this revision to Diff 541983.Jul 19 2023, 6:14 AM

Address Mariya & Aaron's feedback

cor3ntin marked 5 inline comments as done.Jul 19 2023, 7:15 AM
cor3ntin added inline comments.
clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
245–246

I've improved, i hope that works for you!

cor3ntin marked an inline comment as done.Jul 19 2023, 7:34 AM
cor3ntin added inline comments.
clang/include/clang/AST/RecursiveASTVisitor.h
2730–2735

Note this is one of the reasons we didn;t get better diagnostics. It's adding there assuming this was an oversight.
Indeed, i see no reason CXXDefaultInitExpr should not be traversed if CXXDefaultArgExpr should.

cor3ntin updated this revision to Diff 542049.Jul 19 2023, 8:47 AM

Fix test name

aaron.ballman added inline comments.Jul 19 2023, 11:45 AM
clang/lib/Sema/SemaDeclCXX.cpp
2442–2443

Oh wow, good to ignore that suggestion then! :-D

clang/lib/Sema/SemaExpr.cpp
18467

Heh, I went to make the same suggestion on my last round of review, then checked to see what the actual type would be, and went "eh... probably better the way it is". I'm glad I'm not the only one! :-D

clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
222

Ah, I see, because s2 is constant initialized, we *don't* hit the call to side_effect() and so this is a valid initialization. I think my brain read the code in f2() wrong previously, sorry for that!

245

Thank you for the new diagnostic wording, I think it's a good improvement! However, should the second note be on the constructor declaration as opposed to the declaration of x?

cor3ntin updated this revision to Diff 542389.Jul 20 2023, 2:28 AM

Correctly track constructor location.

cor3ntin updated this revision to Diff 542435.Jul 20 2023, 5:06 AM

Remove an assert (added in a previous iteration of this PR).

Unfortunately, we sometimes do emit the address of an immediate function,
after we establish the program is ill-formed - as we still proceed to code gen.

cpp

consteval int id(int i) { return i; }
constexpr int f(auto t) { 
    return t + id(t); 
}

auto b = &f<int>;

We establish taking the address of &f<int> is not possible
when exiting the evaluation context.
At this point b has an init expression, which codegen will try to emit.
Ideally we should try to remove the init all together after the fact but so far
I have not been able to do so.

Remove an assert (added in a previous iteration of this PR).

Unfortunately, we sometimes do emit the address of an immediate function,
after we establish the program is ill-formed - as we still proceed to code gen.

cpp

consteval int id(int i) { return i; }
constexpr int f(auto t) { 
    return t + id(t); 
}

auto b = &f<int>;

We establish taking the address of &f<int> is not possible
when exiting the evaluation context.
At this point b has an init expression, which codegen will try to emit.
Ideally we should try to remove the init all together after the fact but so far
I have not been able to do so.

The removed code was this:

llvm::Constant *CodeGenModule::GetAddrOfFunction(GlobalDecl GD,
                                                 llvm::Type *Ty,
                                                 bool ForVTable,
                                                 bool DontDefer,
                                              ForDefinition_t IsForDefinition) {
  assert(!cast<FunctionDecl>(GD.getDecl())->isImmediateFunction() &&
         "an immediate function should never be emitted");

which seems like very reasonable assertion to have. Should we be marking the init expression/declaration as invalid to force codegen not to run on the expression? So it's not removing the init, but is marking the declaration its attached to as invalid. CC @efriedma @rjmccall

Should we be marking the init expression/declaration as invalid to force codegen not to run on the expression? So it's not removing the init, but is marking the declaration its attached to as invalid.

We should remove the init from the declaration entirely, the tricky part is find the declaration. I did try but so far failed. It's an issue we can track and explore separately from this PR

aaron.ballman accepted this revision.Jul 21 2023, 8:04 AM

Changes LGTM as-is, I'm okay with tracking the assertion question separately. Please give @Fznamznon a chance to check that her concerns have all been addressed before landing.

This revision is now accepted and ready to land.Jul 21 2023, 8:04 AM

Just a couple of NITs otherwise LGTM

clang/lib/Sema/SemaDeclCXX.cpp
2506
2546

Maybe it makes sense to drop const from FD parameter instead of doing a const_cast?

clang/test/SemaCXX/cxx2a-consteval-default-params.cpp
48–49

Maybe it makes sense to print InitWithLambda::InitWithLambda so it looks same as "call to immediate function ... is not a constant expression" message and makes it more obvious.

cor3ntin added inline comments.Jul 24 2023, 4:56 AM
clang/test/SemaCXX/cxx2a-consteval-default-params.cpp
48–49

I don't disagree but for some reason default constructors are printed that way... I'd have to make the diagnostics even more complicated and it did not feel worth it.

Fznamznon added inline comments.Jul 24 2023, 5:03 AM
clang/test/SemaCXX/cxx2a-consteval-default-params.cpp
48–49

Ok, I see! Yes, I don't feel that making it more complicated is worth it too.

cor3ntin updated this revision to Diff 543476.Jul 24 2023, 5:05 AM
  • Fix FunctionDecl constness
  • Replan an auto
Fznamznon accepted this revision.Jul 24 2023, 8:26 AM
This revision was landed with ongoing or failed builds.Jul 24 2023, 9:16 AM
This revision was automatically updated to reflect the committed changes.