-
Notifications
You must be signed in to change notification settings - Fork 11.9k
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
[Clang] Add __builtin_counted_by_ref builtin #114495
base: main
Are you sure you want to change the base?
Conversation
The __builtin_counted_by_ref builtin is used on a flexible array pointer and returns a pointer to the "counted_by" attribute's COUNT argument, which is a field in the same non-anonymous struct as the flexible array member. This is useful for automatically setting the count field without needing the programmer's intervention. Otherwise it's possible to get this anti-pattern: ptr = alloc(<ty>, ..., COUNT); ptr->FAM[9] = 42; /* <<< Sanitizer will complain */ ptr->count = COUNT; To prevent this anti-pattern, the user can create an allocator that automatically performs the assignment: #define alloc(TY, FAM, COUNT) ({ \ TY __p = alloc(get_size(TY, COUNT)); \ if (__builtin_counted_by_ref(__p->FAM)) \ *__builtin_counted_by_ref(__p->FAM) = COUNT; \ __p; \ }) The builtin's behavior is heavily dependent upon the "counted_by" attribute existing. It's main utility is during allocation to avoid the above anti-pattern. If the flexible array member doesn't have that attribute, the builtin becomes a no-op. Therefore, if the flexible array member has a "count" field not referenced by "counted_by", it must be set explicitly after the allocation as this builtin will return a "nullptr" and the assignment will most likely be elided.
…d ignores pretty much everythings that may 'hide' the underlying MemberExpr.
…t the return type based on the 'count' field's type.
FindCountedByField can be used in more places than CodeGen. Move it into FieldDecl to avoid layering issues.
…on-pathological expressions.
…d / or count field.
…. Go back to 'size_t *' as the default return type.
@AaronBallman @rapidsna -- I think I covered all of the comments you made on the other PR (stupid git!). PTAL. |
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.
Can you add a test with -ast-print
that shows we print out the builtin as it's used in the source, and a test with -ast-dump
that shows the AST retains information about the use of the builtin?
-ast-print testcase added. |
*_Generic( | ||
__builtin_counted_by_ref(&p->array[0]), | ||
void *: &__ignored, | ||
default: __builtin_counted_by_ref(&p->array[idx++])) = 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.
I see you have a test with a side effect. Though idx++
is a parameter so the side effect would be optimized out in this O2 test anyway even if it was codegen'ed. Could you please use something that won't be optimized out to make sure the test is really checking it's not codegen'ed?
Also, is it intentionally to make &p->array[non-zero]
work?
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.
Wait, why are we doing -O2
in a test to begin with? Afaik codegen tests are usually supposed to run w/o optimisation; otherwise, you just end up testing the optimiser, and that’s not what Clang’s regression tests are for, after all.
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 agree.
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 removed the -O2
now. So the idx
shouldn't be optimized away.
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! Do you have a test when the base of member has a side effect? e.g., __builtin_counted_by_ref(foo().array)
The behavior seems to be different than idx++
case, because when the base has a side effect the codegen seems to return (void *)0
instead of returning the count reference. My inclination is to reject code or emitting a different warning message when the base has a side effect because the programmer might want to fix that instead of it being silently ignored or confused about the behavior.
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 I do something like this:
*_Generic(
__flex_counter((p++)->array),
void *: &__ignored_assignment,
default: __flex_counter(p->array)) = 42;
The code generation seems to take it in stride (with a warning of course):
define dso_local noalias noundef ptr @alloc_s(i32 noundef %size) local_unnamed_addr #0 {
entry:
%0 = load ptr, ptr @p, align 8, !tbaa !5
%..counted_by.gep = getelementptr inbounds i8, ptr %0, i64 4
store i8 42, ptr %..counted_by.gep, align 1, !tbaa !9
ret ptr null
}
I'm not sure if that's good or bad. In truth, the warning should probably be upgraded to an error, as it will lead to unexpected behavior if the warning is ignored. Thoughts?
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, upgrading to an error sounds good to me! Also, the message might need to be updated (since the side effect is not actually ignored).
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.
Done.
@@ -16,6 +16,8 @@ void test1(struct fam_struct *ptr, int size, int idx) { | |||
|
|||
*__builtin_counted_by_ref(ptr->array) = size; // ok | |||
*__builtin_counted_by_ref(&ptr->array[idx]) = size; // ok | |||
*__builtin_counted_by_ref(&ptr->array) = size; // ok |
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 it okay? Or should we make it an ill-form?
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 looks like it generates correct code:
{
unsigned long int __ignored_assignment;
*_Generic(
__flex_counter(&p->array),
void *: &__ignored_assignment,
default: __flex_counter(&p->array)) = 42;
}
/*
define dso_local noalias noundef ptr @alloc_s(i32 noundef %size) local_unnamed_addr #0 {
entry:
%0 = load ptr, ptr @p, align 8, !tbaa !5
%..counted_by.gep = getelementptr inbounds i8, ptr %0, i64 4
store i8 42, ptr %..counted_by.gep, align 1, !tbaa !9
ret ptr null
}
*/
But if you prefer it to be ill-formed, then I can do 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.
I guess this will just work too? *__builtin_counted_by_ref(ptr->array[idx]) = size;
Do you have a use case? I'd prefer these being ill-formed because the user might find it working unexpectedly. But I don't have a strong opinion on this. I'd leave it up to you/Linux users if you find this would be useful.
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.
*__builtin_counted_by_ref(ptr->array[idx]) = size;
doesn't work because the builtin requires a pointer to the FAM. (If you meant &ptr->array[idx]
, that does work.)
I don't have any use case for &ptr->array
or the like. I just don't want to be overly restrictive on what's considered a valid argument to the builtin. I think I'll do a comparison with gcc's version and see what they accept. We can match them.
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.
GCC rejects everything that isn't an array. So not even &ptr->array[0]
. I guess we can do 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.
Note that, because GCC only checks if the argument is an array, it allows for things like:
int global_array[];
void foo(int val) {
*__builtin_counted_by_ref(global_array) = val;
}
which isn't great.
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 we can still keep it an error and have GCC to reflect our model on this one, unless Linux relies on 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.
Yeah. I don't want to allow for global arrays either, just flexible array members.
Okay. I removed the |
void test3(struct fam_struct *ptr, int idx) { | ||
__builtin_counted_by_ref(&ptr->array[idx]); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}} | ||
__builtin_counted_by_ref(&ptr->array); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}} | ||
__builtin_counted_by_ref(ptr->x); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}} |
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.
Nit: we can also add __builtin_counted_by_ref(ptr->array[0]);
I still see |
Yeah, the |
The __builtin_counted_by_ref builtin is used on a flexible array
pointer and returns a pointer to the "counted_by" attribute's COUNT
argument, which is a field in the same non-anonymous struct as the
flexible array member. This is useful for automatically setting the
count field without needing the programmer's intervention. Otherwise
it's possible to get this anti-pattern:
To prevent this anti-pattern, the user can create an allocator that
automatically performs the assignment:
The builtin's behavior is heavily dependent upon the "counted_by"
attribute existing. It's main utility is during allocation to avoid
the above anti-pattern. If the flexible array member doesn't have that
attribute, the builtin becomes a no-op. Therefore, if the flexible
array member has a "count" field not referenced by "counted_by", it
must be set explicitly after the allocation as this builtin will
return a "nullptr" and the assignment will most likely be elided.