-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[clang] WIP: Improved Context Declaration tracking #107942
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
base: main
Are you sure you want to change the base?
Conversation
This patch aims to improve how parenting relationships are represented in the AST. Currently regular declarations can only be children of special AST nodes which inherit from DeclContext, except for a few special cases which are required by itanium mangling. Having this parenting relationship well described helps in tracking which entities are dependent, and an improvement here would allow us to drop a lot of workarounds and to get some difficult cases right. This patch extends the ContextDecl tracking which currently is used for mangling, in order to cover almost needed cases. Template type aliases represent a specially difficult case which is addressed by this patch. They can be contexts for other declarations, but besides not being DeclContexts, they also lack a declaration which represents their specialization. This patch addresses the type alias context problem by storing the specialization arguments along with the ContextDecl for entities declared in their context. TODO: * Move away from using ExpressionEvaluationContexts, and use the same stack used for tracking the CurContext. Remove the separate ContextDecl field, and have that functionally built in to the base Decl class, so that we don't separatelly treack both a parent Decl and a parent DeclContext. * A function type has ParmVarDecls, and these can appear inside aliases and template type aliases in particular, so a ParmvarDecl needs a ContextDecl too.
// so early, e.g. because we want to see if any of the *substituted* | ||
// parameters are dependent. | ||
DependencyKind = getDerived().ComputeLambdaDependency(&LSICopy); | ||
Class->setLambdaDependencyKind(DependencyKind); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So after the removal of such, lines 14911 - 14914 would also be useless.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Just some thoughts/questions in my mind, and I may need more time to understand the PR.
@@ -6291,7 +6307,8 @@ bool Parser::isDeclarationSpecifier( | |||
bool Parser::isConstructorDeclarator(bool IsUnqualified, bool DeductionGuide, | |||
DeclSpec::FriendSpecified IsFriend, | |||
const ParsedTemplateInfo *TemplateInfo) { | |||
RevertingTentativeParsingAction TPA(*this); | |||
RevertingTentativeParsingAction TPA(*this, /*Unannotated=*/true); | |||
Sema::SFINAETrap Trap(Actions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need a SFINAETrap here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To suppress a bunch of diagnostics that would otherwise be duplicated when we parse the thing again for real.
@@ -961,6 +961,7 @@ static const Expr *SubstituteConstraintExpressionWithoutSatisfaction( | |||
|
|||
if (MLTAL.getNumSubstitutedLevels() == 0) | |||
return ConstrExpr; | |||
MLTAL.setKind(TemplateSubstitutionKind::Rewrite); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I presume this avoids creating Subst* nodes for the substitution, right?
Could you explain what we would gain from / the difference it makes this way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember I had tried using RewriteKind while fixing the mismatched constraint issue. But that turned out to cause some regressions/crashes in the end. Don't know what kind of situation it would be with this patch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new difference introduced in this patch is that a Rewrite is depth preserving, while a specialization is not.
In this case, we want to rewrite a requires clause from another potential redeclaration to see if it matches, and confirm it really is a redeclaration.
If we don't preserve the depth here, this would trip a new assert, where we would be instantiating a requires clause at template depth 0, but then parenting it to a declaration at depth 1, which is incorrect.
if (auto *TD = dyn_cast<FunctionTemplateDecl>(D)) | ||
D = TD->getTemplatedDecl(); | ||
else | ||
assert(isa<FunctionDecl>(D)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe
assert((isa<FunctionDecl, FunctionTemplateDecl>(D)));
if (auto *FTD = dyn_cast<>)
...
?
static auto skipRequiresBody(DeclContext *DC) { | ||
while (DC->isRequiresExprBody()) | ||
DC = DC->getParent(); | ||
return DC; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could move it to tryCaptureVariable
, inlining it as a lambda to avoid such small functions.
// Exception: Function parameters are not tied to the function's DeclContext | ||
// until we enter the function definition. Capturing them anyway would result | ||
// in an out-of-bounds error while traversing DC and its parents. | ||
if (isa<ParmVarDecl>(Var) && !VarDC->isFunctionOrMethod()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the rationale behind it is that every ParmVarDecl now has a context decl pointing to e.g. its function? Do we still need to set the ParmVarDecl's context separately (by calling setOwningFunction
) now?
Sorry I haven't gotten around to the changes on VarDecls yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still do need to reparent them the old way, but this is going to change once I give them a ContextDecl too.
The difference right now is that we don't create the ParmVarDecls always at the translation unit context anymore, we just create them at the CurContext.
@@ -19000,7 +19056,7 @@ bool Sema::tryCaptureVariable( | |||
Explicit = false; | |||
FunctionScopesIndex--; | |||
if (IsInScopeDeclarationContext) | |||
DC = ParentDC; | |||
DC = skipRequiresBody(ParentDC); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, do you have a test for it? e.g. the requires expression contains a lambda that contains another requires expression which would cause a crash for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is one of your tests actually:
clang/test/SemaCXX/lambda-unevaluated.cpp
// GH88081: Test if we evaluate the requires expression with lambda captures properly.
namespace GH88081 {
// Test that ActOnLambdaClosureQualifiers() is called only once.
void foo(auto value)
requires requires { [&] -> decltype(value) {}; }
// expected-error@-1 {{non-local lambda expression cannot have a capture-default}}
{}
The difference is that now the requires expression is properly reparented to foo
, where as before it would just sit at the namespace.
ExprEvalContexts.back().ManglingContextDecl)) { | ||
if (auto ContextDecl = currentEvaluationContext().ContextDecl; | ||
auto *VD = | ||
dyn_cast_or_null<VarDecl>(ContextDecl ? *ContextDecl : nullptr)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dyn_cast_if_present, if we have to touch it anyway :)
const auto &PrevRec = ExprEvalContexts.back(); | ||
PushExpressionEvaluationContext(NewContext, PrevRec.ContextDecl, | ||
PrevRec.ContextArgs, ExprContext); | ||
ExprEvalContexts.back().HasReusedDeclContext = true; | ||
ExprEvalContexts.back().LazyContextDeclPos = PrevRec.LazyContextDeclPos; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, PrevRec
would be invalidated after PushExpressionEvaluationContext()
, so you probably want to save the value before the call.
This caused clang/test/SemaTemplate/stack-exhaustion.cpp
to fail on my machine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I saw this failure on CI, but not on my machine (MacOS).
Thanks for taking a look.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took another pass through the code, and it looks good overall.
Given the scale of this patch, input from others would be valuable. It would be nice if you could move the patch out of draft status to get more visibility :)
@@ -7946,6 +7980,8 @@ class Sema final : public SemaBase { | |||
/// A stack of expression evaluation contexts. | |||
SmallVector<ExpressionEvaluationContextRecord, 8> ExprEvalContexts; | |||
|
|||
SmallVector<Decl *, 8> PendingLazyContextDecls; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So from a quick reading, PendingLazyContextDecls is used for lambdas created as part of other declarations, such as being part of a default argument for a template parameter. Their context declarations are updated only at the end of the evaluation context or right after the outer declaration is created.
I think we'd better document its intent, wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intention is that they are used for any declarations where its parent is created later. Sure will document this.
ContextDecl.hasValue() ? *ContextDecl | ||
: ContextDeclOrSentinel(TemplateDepth), | ||
currentEvaluationContext().ContextArgs); | ||
if (!ContextDecl.hasValue()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it more likely for one to conflate the semantics of hasValue()
with those of operator bool
whereas the latter actually indicates whether this is in a null state (i.e. neither the pointer nor the depth is assigned).
For clarity, could we rename it to something more descriptive, like hasDeclPointer()
?
else if (!(isa<FunctionDecl>(D) || isa<VarDecl>(D) || | ||
isa<VarTemplateSpecializationDecl>(D))) { | ||
D->dumpColor(); | ||
llvm_unreachable("FU"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FU?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debugging leftover, will remove.
assert(Rec.LazyContextDeclPos <= PendingLazyContextDecls.size()); | ||
if (!Rec.HasReusedDeclContext) { | ||
if (Rec.ContextDecl.hasValue()) { | ||
assert(Rec.LazyContextDeclPos == PendingLazyContextDecls.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this an assumption for that
-
if the lambda (or something else we want to later add context decl for) is aware of its parent context decl at the point of its creation, then there should not be any pointer in
PendingLazyContextDecls
tracking the lambda. -
the subsequent on-stack evaluation contexts should have handled PendingLazyContextDecls properly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's basically it. If we have already created the parent, we shouldn't be using the Pending list at all.
If this originally started as a lazy context, then we are in a state where we know the parent, so we should have already handled and emptied the lazy list, and shouldn't be adding new stuff to it anymore.
} else { | ||
while (PendingLazyContextDecls.size() > Rec.LazyContextDeclPos) { | ||
Decl *D = PendingLazyContextDecls.pop_back_val(); | ||
setContextDecl(*this, D, nullptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anything special for the third argument being null? Both setLambdaContextDecl()
and handleLambdaNumbering()
would bail out for such cases, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the error path. Basically we had a bunch of declarations waiting for their parents to be created, but for some reason we bailed out and we are never going to create their parents anymore.
Before this patch, these declarations would have stayed parented to their parent's parent.
These declarations would never make it to codegen, so don't matter much except for analysis.
The upside is that by staying as lazycontext entities, we don't lose track of their template depth.
LocalInstantiationScope InstScope(getActions()); | ||
for (ParmVarDecl *D : LocalParameterDecls) | ||
InstScope.InstantiatedLocal(D, D); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where would we use that?
Noted. Sorry I haven't responded yet, I have other priorities and it might take a while before I get back to this patch. |
This patch aims to improve how parenting relationships are represented in the AST.
Currently regular declarations can only be children of special AST nodes which inherit from DeclContext, except for a few special cases which are required by itanium mangling.
Having this parenting relationship well described helps in tracking which entities are dependent, and an improvement here would allow us to drop a lot of workarounds and to get some difficult cases right, such as lambdas and requires expressions appearing within return types and function parameter types of templated functions.
This patch extends the ContextDecl tracking which currently is used for mangling, in order to cover almost needed cases.
Template type aliases represent a specially difficult case which is addressed by this patch.
They can be contexts for other declarations, but besides not being DeclContexts, they also lack a declaration which represents their specialization.
This patch addresses the type alias context problem by storing the specialization arguments along with the ContextDecl for entities declared in their context.
TODO:
Move away from using ExpressionEvaluationContexts, and use the same stack used for tracking the CurContext. Remove the separate ContextDecl field, and have that functionally built in to the base Decl class, so that we don't separatelly track both a parent Decl and a parent DeclContext.
A function type has ParmVarDecls, and these can appear inside aliases and template type aliases in particular, so a ParmVarDecl needs a ContextDecl too.