-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[clang] Handle trivial_abi attribute for Microsoft ABI. #88857
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?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -std=c++11 -fcxx-exceptions -fexceptions -emit-llvm -o - %s | FileCheck %s | ||
|
|
||
| // CHECK: %[[STRUCT_TRIVIAL:.*]] = type { ptr } | ||
| struct __attribute__((trivial_abi)) Trivial { | ||
| int *p; | ||
| Trivial() : p(0) {} | ||
| Trivial(const Trivial &) noexcept = default; | ||
| }; | ||
|
|
||
| // CHECK-LABEL: define{{.*}} i64 @"?retTrivial@@YA?AUTrivial@@XZ"( | ||
| // CHECK: %retval = alloca %[[STRUCT_TRIVIAL]], align 8 | ||
| // CHECK: %call = call noundef ptr @"??0Trivial@@QEAA@XZ"(ptr noundef nonnull align 8 dereferenceable(8) %retval) | ||
| // CHECK: %coerce.dive = getelementptr inbounds %[[STRUCT_TRIVIAL]], ptr %retval, i32 0, i32 0 | ||
| // CHECK: %0 = load ptr, ptr %coerce.dive, align 8 | ||
| // CHECK: %coerce.val.pi = ptrtoint ptr %0 to i64 | ||
| // CHECK: ret i64 %coerce.val.pi | ||
| Trivial retTrivial() { | ||
| Trivial s; | ||
| return s; | ||
| } | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For completeness, please add the instance and static method return cases, since those are indirect for other reasons. For me, it's sufficient to check the function prototype, both of which should have an sret pointer:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I checked your example there and the instanceMethod is no problem, but I noticed that the staticMethod doesn't contain a
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I stand corrected, but I think it makes the point that this logic can be confusing, and it's worth testing. :) |
||
| struct TrivialInstance { | ||
| Trivial instanceMethod(); | ||
| static Trivial staticMethod(); | ||
| }; | ||
|
|
||
| // We need to make sure that instanceMethod has a sret return value since `this` will always go in the register. | ||
| // CHECK-LABEL: define{{.*}} void @"?instanceMethod@TrivialInstance@@QEAA?AUTrivial@@XZ"({{.*}} sret(%struct.Trivial{{.*}} | ||
| Trivial TrivialInstance::instanceMethod() { return {}; } | ||
| // CHECK-LABEL: define{{.*}} i64 @"?staticMethod@TrivialInstance@@SA?AUTrivial@@XZ"( | ||
| Trivial TrivialInstance::staticMethod() { return {}; } | ||
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've realized this misses a case, consider this a wrapper struct that contains a trivial_abi subobject: https://godbolt.org/z/bK6aGv4d7
I spent 15 minutes looking through the things we track for trivial_abi, and unfortunately, we don't track this anywhere already, so the best solution I can come up with is to do a recursive walk of all the subobjects and look for the attribute. :(
Uh oh!
There was an error while loading. Please reload this page.
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's not how trivial_abi normally works? You'll never even reach this code for anything with a non-trivial destructor that isn't explicitly marked trivial_abi.
I guess if you're abusing trivial_abi just to get the MSVC-specific behavior in this patch, you might want that recursive behavior, but that seems like it's stretching it a bit. We might want to consider a dedicated attribute.
On a related note, if we're going to mess with this anyway, we might want to mess with the canPassInRegisters() in clang/lib/Sema/SemaDeclCXX.cpp also, so structs with a deleted copy constructor work, for example:
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 it is how it works, based on re-reading the code just now and the test case I linked.
To your point, the MSVC-specific code in
canPassInRegistersis full of bugs, but I don't want to ask @tru to deal with that.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.
Oh, I see, whether the constructors/destructor/etc. are trivial for calls is determined recursively; we don't record whether that was because of an explicit trivial_abi attribute, or because there just weren't any non-trivial fields. So we can't tell the difference here.
It feels sort of weird to treat a struct with a trivial_abi member as somehow more trivial than a struct that just contains plain data.
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 if I understand correctly:
Seems like there are many more places where we can do better, do we want to block this PR on the recursive search? What would the rules for that be? It can't just be that if you have a struct with a trivial_abi attribute inside any struct the parent should also be considered trivial. Would it be that isTrivialForMicrosoft would return true if the current struct passes the checks there and only contains a struct with a trivial_abi attribute?
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.
Yes, I think I do want to block this on the recursive search. Even if it's ugly and we ultimately replace it with an explicitly tracked and type trait (
containsTrivialAbiSubobject?), it reduces the number of externally visible ABI changes. I know we're saying this is ABI unstable already, but I still think fewer breaks is better.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.
Happy to add this. Can you point me to an example where we search recursively or give some pseudo code example and I should be able to take it from there pretty soon.
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 pseudo code version is something like:
bool containsTrivialAbiSubobject(CXXRecordDecl RD) {
if (RD->hasAttr()) return true;
for (auto B : RD->bases()) {
if (containsTrivialAbiSubobject(B->getAsCXXRecordDecl()))
return true;
}
for (auto F : RD->fields()) {
const CXXRecordDecl FRD = F->getType()->getAsCXXRecordDecl();
if (FRD && containsTrivialAbiSubobject(FRD))
return true;
}
return false;
}
A bit of a gross inefficient DFS, with some CXXBaseSpecifier details omitted. It might also be worth tracking Visited records to handle fields of the same record type without recursing over all subobjects again.