-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[Clang] [Sema] Improve support for __restrict
-qualified member functions
#83855
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
…tions in canonical type
Sorry for the rather humongous pr description, but I tried to handle every edge case I could come up with (as well as include my rationale as to why I’ve decided to handle them that way)—thought it’s still possible that I missed some. |
// Propagate '__restrict' properly. | ||
if (auto MD = dyn_cast<CXXMethodDecl>(Function)) { | ||
bool Restrict = cast<CXXMethodDecl>(PatternDecl)->isEffectivelyRestrict(); | ||
if (Restrict != MD->getMethodQualifiers().hasRestrict()) { | ||
const auto *FPT = MD->getType()->getAs<FunctionProtoType>(); | ||
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo(); | ||
if (Restrict) | ||
EPI.TypeQuals.addRestrict(); | ||
else | ||
EPI.TypeQuals.removeRestrict(); | ||
MD->setType(Context.getFunctionType(FPT->getReturnType(), | ||
FPT->getParamTypes(), EPI)); | ||
} | ||
} | ||
|
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.
If there is a better place to put this, please let me know. This works, but I at least feel like we should be doing this somewhere earlier...
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.
It seems to me that this should happen on the Function Declaration instantiation, not the defintion. Since this should propagate even if the function is not yet defined.
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.
Actually, I think that’s not it. I got confused by this too just now: this here is about propagating __restrict
-ness (or lack thereof since we may have to remove it again if the declaration is __restrict
, but the definition isn’t, and we’re in GCC mode) to the definition of the function only.
Specifically, we can’t just make the declaration __restrict
when we first create it; the declaration needs to be __restrict
iff the template declaration is __restrict
, and the definition needs to be __restrict
iff the template defintion is __restrict
; the former should happen automatically as part of template instantiation; this here is only about the latter.
I’m going to expand this comment a bit more to elaborate on this.
Reported 2 bugs for MSVC. (The first also affects standard cv-qualifiers.) |
Update: it seems that the GCC behaviour is intended, and it actually makes a lot of sense now imo at least (see the initial pr comment for more info about this). |
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 did a review, but didn't stop to decide whether we SHOULD :) Aaron should be around 'sometime' to make that judgement, my restrict knowledge/reasoning is slim/none.
// Propagate '__restrict' properly. | ||
if (auto MD = dyn_cast<CXXMethodDecl>(Function)) { | ||
bool Restrict = cast<CXXMethodDecl>(PatternDecl)->isEffectivelyRestrict(); | ||
if (Restrict != MD->getMethodQualifiers().hasRestrict()) { | ||
const auto *FPT = MD->getType()->getAs<FunctionProtoType>(); | ||
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo(); | ||
if (Restrict) | ||
EPI.TypeQuals.addRestrict(); | ||
else | ||
EPI.TypeQuals.removeRestrict(); | ||
MD->setType(Context.getFunctionType(FPT->getReturnType(), | ||
FPT->getParamTypes(), EPI)); | ||
} | ||
} | ||
|
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.
It seems to me that this should happen on the Function Declaration instantiation, not the defintion. Since this should propagate even if the function is not yet defined.
ping |
@Sirraide, wait, did we all miss this PR? I am so sorry |
This was a while ago, but I recall talking to @AaronBallman about this one several times, and I think our conclusion at some point was that it wasn’t worth supporting this divergent behaviour or sth like that but I could be wrong (we also had a discussion about in one of the Wednesday meetings iirc). |
That matches my recollection. Use of |
The exact semantics of
__restrict
when applied to member functions are somewhat nebulous seeing as it is a compiler extension, and as a result, there are quite a few edge cases that our implementation currently doesn’t handle very well (or at all). This pr is mainly about cases such as:Both GCC and MSVC support this extension, and so does Clang; the problem is that our support for it is quite incomplete in some cases. Fortunately, GCC and MSVC agree on surprisingly many things here, but there are still enough places where they differ and which we’re not handling properly at the moment.
At the same time, it should be noted that MSVC’s documentation surprisingly states that ‘The
__restrict
keyword is valid only on variables’, despite the fact that MSVC seems to support it on functions anyway (to the point where it affects how function names are mangled). The remainder of this discussion assumes that it is indeed supported and that the documentation is incomplete or incorrect.__restrict
in a declaration vs definitionFirst, both GCC and MSVC allow a mismatch in
__restrict
-qualification between the declaration and definition of a member function:Unfortunately, that is where the similarities end; GCC only seems to care about
__restrict
being present on the definition;I’ve already opened a GCC bug report for that to ask for clarification whether this is actually intended.Update: It seems that my interpretation of this was just wrong: GCC handles__restrict
in this position just like any other cv-qualifier on a function parameter—i.e. it only matters if it’s specified in the definition; the confusing part about this to me was that it’s syntactically in the same position asconst
andvolatile
, but that’s about the only thing it has in common with those two. So yes, this behaviour definitely makes sense then if you look at it that way.MSVC does the opposite, to the point where the function name is mangled differently depending on whether
__restrict
is present on the declaration (__restrict
on an out-of-line definition does not impact mangling at all and seems to be ignored entirely; see the section onthis
below). The fact that it has an effect on mangling seems to indicate that ignoring__restrict
if it is only present on a function definition is intended, as you’d run into linker errors otherwise.We currently do not support this at all. The best approach to fixing this currently seems to be simply ignoring
__restrict
when checking if member function types are compatible.Also, to make talking about this a bit easier, let’s call a member function ‘effectively
__restrict
’,__restrict
; orMSVCCompat
mode, if its declaration contains__restrict
; or__restrict
.__restrict
member function pointersThis also has consequences on how
__restrict
-qualification is handled in pointers to member functions: both GCC and MSVC basically ignore__restrict
in a pointer to member type entirely:At least with GCC, this behaviour seems intended, as its documentation explicitly states that:
Expectedly,
__is_same
also returnstrue
in these cases in GCC (MSVC does not have__is_same
). To support this behaviour, we should strip__restrict
from the type of a pointer to member function; I’ve been talking this over with @AaronBallman for quite a bit, and we both agree that attempting to instead keep it and adjust all places where it might matter to ignore it seems infeasible.However, we cannot simply strip
__restrict
from all function types because of another annoying detail that GCC and MSVC also both agree on:__restrict
on regular function types does matter:Fortunately, what seems to be working quite well is stripping
__restrict
from the function type whenever we create aMemberPointerType
—this does mean we lose out on source fidelity a bit though—and ignoring__restrict
checking if two function types are compatible for the purpose of merging function declarations.Issues with other approaches
Aaron and I considered the possibility of sometimes stripping
__restrict
from the canonical type of a function type, but I ran into two issues with that:getFunctionTypeInternal()
, which has no idea whether we’re about to use this thing in aMemberPointerType
, so doing this would entail updating every last use ofgetFunctionType()
, which would not only be a lot of work, but also very error-prone.void f() __restrict
, for which we then build the typevoid () __restrict
; let’s call that type(A)
; its canonical type, by this current approach, would bevoid f()
. Now, what happens if we encounter, sayusing X = void () __restrict
. This will try to constructvoid () __restrict
, which is the same type as(A)
! Since it already exists, we reuse(A)
, but oh no, its canonical type doesn’t have__restrict
even though it should in this case...Another approach I considered but was quick to discard as well was always stripping
__restrict
from the function type of aCXXMethodDecl
and tracking it as a bit in theCXXMethodDecl
itself; however, every place that checks whether aCXXMethodDecl
is__restrict
would have to be updated as well, which is probably doable, but as Aaron also pointed out to me when I suggested that, we’d rather not have both an ‘is__restrict
’ bit on the function type as well as an ‘is actually__restrict
’ bit inCXXMethodDecl
(or its type) if we can avoid it, because keeping those two in sync (or rather, remembering to use the latter as the former would basically never be set under that approach) sounds like it would only make things even more confusing than they already are.In conclusion, stripping it when creating a
MemberPointerType
only and keeping it on the type of aCXXMethodDecl
and just manually ignoring it in cases where other compilers ignore it seems to be the most feasible approach.this
Note that
__restrict
differs fromconst
andvolatile
in that same position because, whereas the latter two apply to the object pointed to bythis
—i.e.decltype(this)
would beconst S*
in aconst
member function ofS
—__restrict
, on the other hand, is applied to thethis
pointer itself, i.e.decltype(this)
isS* __restrict
(not__restrict S*
):We recently merged a pr (#83187) that fixed some bugs regarding this.
Both compilers also agree that
this
is__restrict
iff the member function is effectively__restrict
; the rest of the examples below apply to code inside an effectively__restrict
member function.Unfortunately, lambdas make this a bit more complicated: if
this
is not captured by value, then it still points to the same object, and it stands to reason that the capturedthis
would still be__restrict
. GCC agrees with this and so does MSVC (with a caveat, see below):If we capture
this
by value, the situation is different; now,this
refers to a different object, which means that thisthis
is a different pointer, which would imply that we should no longer consider it__restrict
; GCC agrees with that; however, MSVC still thinks it is__restrict
:I mentioned a caveat with MSVC, and you may have noticed that I switched to using
is_same<>::value
instead ofis_same_v<>
for these last two assertions; this is because, inside a lambda that capturesthis
(by value or by reference), MSVC seems to disagree with itself whether or notthis
is__restrict
:If this is not an MSVC bug, then I don’t know what it is, candidly. The reason I said earlier that MSVC does seem to consider
this
to be__restrict
(in both cases) is because attempting to instantiate an incomplete template gives this error (a failingstatic_assert
unfortunately only prints ‘static assertion failed’ with no extra information with MSVC, necessitating this workaround):Additionally, there is another MSVC issue I’ve run into: for the life of me, I cannot get
decltype(&S::a)
(whereS::a
is__restrict
) to compare equal to any other type; theIncomplete
trick above givesvoid (__cdecl S::* )(void)
, but even that does not work. I suspect this is another MSVC bug.Additional notes
Templates and explicit template specialisations behave just as though they were regular member functions wrt
__restrict
in both GCC and MSVC. Furthermore, the presence of other qualifiers (e.g.const
or&&
) does not seem to have any impact on the behaviour of__restrict
.This entire analysis is based on what does and does not compile, or what types are or aren’t equal. I have not taken into consideration how the presence of
__restrict
does or does not impact codegen in either GCC or MSVC, and I don’t believe there is a good reason to do that, seeing as this is mainly about ensuring that we agree with GCC/MSVC on what type something is.That is, whether we actually do anything based on whether e.g.
__restrict
is only present on the declaration or definition is up to us, and we don’t necessarily need to agree with other compilers here. After all, it’s not like we’re ever just conjuring a__restrict
out of thin air: if codegen ends up being different because we think a function is__restrict
, then that means a user put__restrict
somewhere, and if they didn’t want it to be__restrict
, they shouldn’t have written__restrict
anywhere in the first place. This pr is entirely about ensuring that code that compiles with GCC or MSVC compiles and does something valid when compiled with Clang.Related issues wrt
__restrict
MSVC (but not GCC) allows constructors and destructors to be marked as
__restrict
(see #27469). I would argue we should at least support this inMSVCCompat
mode, but I’m not opposed to also just supporting it in general (note that, although cv-qualifiers are not allowed on constructors/destructors, I would argue that that is because it just doesn’t make sense for an object that is being constructed/destroyed to be e.g.const
; however,__restrict
is different because it applies tothis
, and not*this
, so there may be situations where there might be value in making it__restrict
).As an aside, since we’re already talking about
__restrict
, MSVC allows aT*&
to bind to aT* __restrict
(see #27383 and maybe #63662), i.e. the following code is accepted by MSVC:GCC and Clang both reject this, and it seems semantically wrong to allow this (GCC and Clang also both complain about this in C, where
restrict
is actually part of the standard). There could be a case for supporting this inMSVCCompat
mode (maybe downgraded to a warning as in C), but I’d leave it as is otherwise (and maybe close the issue(s) in question as wontfix in that case). This should also be a separate pr, if we do decide to change something here; I just wanted to bring this up because, if I’m already improving support for__restrict
, I might as well look into other issues with it.Effects on existing behaviour
Most of the GCC-specific behaviour for
this
has already been implemented by #83187; I’ve refactored the code a bit and added support for MSVC-specific behaviour.Currently, Clang does not support a mismatch in
__restrict
-qualification between the declaration and definition of a member function, which means that allowing that and implementing its semantics properly (‘properly’ as far as we can tell, anyway; all of this is awfully ill-documented...) shouldn’t cause any problems.One thing to note is that stripping
__restrict
in some cases definitely impacts source fidelity; I have yet to look into possible solutions or workarounds for that, but also, I personally would argue that that is less important than getting the semantics of__restrict
right in the first place.To-do list
Supporting GCC’s and MSVC’s semantics wrt
__restrict
seems to entail:__restrict
-qualification in the declaration and definition of a member function. The most straight-forward solution seems to be to ignore__restrict
when merging member function declarations.__restrict
from the function type of aMemberPointerType
whenever we construct one.__restrict
member function of some classS
, makethis
always__restrict
, unless we’re inside a lambda that capturesthis
by value in non-MSVCCompat
mode.__restrict
properly in template instantiation.__restrict
constructors/destructors (possibly only inMSVCCompat
mode).I’ve already implemented the points above; fortunately, I haven’t run into any major issues with the approach detailed above.
Links
This fixes #11039.