Alejandro =?utf-8?q?Álvarez_Ayllón?=,
Alejandro =?utf-8?q?Álvarez_Ayllón?=,
Alejandro =?utf-8?q?Álvarez_Ayllón?Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/119...@github.com>


================
@@ -15919,10 +15919,17 @@ ExprResult Sema::ActOnStmtExprResult(ExprResult ER) {
   if (Cast && Cast->getCastKind() == CK_ARCConsumeObject)
     return Cast->getSubExpr();
 
+  auto Ty = E->getType().getUnqualifiedType();
----------------
AaronBallman wrote:

> > > As far as this patch goes, my longstanding thought is that _Atomic is 
> > > clearly a qualifier.
> > 
> > 
> > Errrr, I don't agree. I think `_Atomic` is a type specifier. `_Atomic int` 
> > and `int` are not the same type
> 
> `const int` and `int` are also not the same type.

Maybe I'm being imprecise, sorry for that. `const int` and `int` are both 
"morally" of type `int` in that the allowed accesses to either type will give 
the same code generation and behavior. There's no difference between read 
accesses to `int` and `const int`: https://godbolt.org/z/GzhKWr8W6

I think `int` and `_Atomic int` are not morally the same types; they have 
different behaviors due to the accesses: https://godbolt.org/z/xf8WEzaM5, they 
can have different sizes, they can have different alignments, etc.

I guess I view `int` vs `_Atomic int` being closer in behavior to `int` vs 
`long long int`.

> > (heck, they can even have different sizes and alignments!).
> 
> Size and alignment is the (back-justified) reason we draw the line where we 
> currently do. It is not a particularly sensible way to do it. Most of our 
> existing qualifiers affect code generation and ABI, just in ways that happen 
> to not affect size and alignment. Several qualifiers affect the ABI rules for 
> containing aggregates, for example.

Do you have an example of impacting the ABI rules in C?

> > It's somewhat like a qualifier in that it impacts how you access the 
> > underlying object (so in that regard, it is a bit like `volatile int` and 
> > `int`), but I think the fundamental thing is that you rarely want `int` 
> > semantics directly when the type is spelled `_Atomic int`.
> 
> I'm not quite sure what you're saying here.

Loading an int to get its value is a different operation than loading an atomic 
int to get its value; the latter may require entirely different instruction 
selection. Probably `int` is a bad example, perhaps `struct` values makes the 
distinction more clear?

> On the implementation level, the compiler can _never_ ignore qualifiers when 
> performing an access to an object; the whole point of qualifiers is that they 
> affect the semantics of accesses. On the language design level, I agree with 
> you that programmers should never perform normal accesses to `_Atomic` 
> objects instead of using the intrinsics. However, that is an argument that it 
> was an unfortunate language design decision that `_Atomic` behaves like a 
> qualifier. In the language that we've actually got, `_Atomic` has most of the 
> characteristics of qualifiers, and there is no benefit to the compiler 
> implementation in pretending otherwise.

Maybe I'm odd (it's known to happen), but I guess I view `_Atomic int` and 
`int` in C the same way as I view `std::atomic<int>` and `int` in C++. While 
there's a lot of similarities between how atomics work and how qualifiers work, 
I instinctively view them as completely separate types.

> The common characteristics of qualifiers:
> 
>     * They convey an aspect of how a value is stored or accessed other than 
> the abstract value itself.

Agreed.

>     * They are typically disallowed or silently dropped at the top level in 
> type contexts that primarily describe an abstract value rather than storage, 
> such as casts, return values, and parameters (when determining the function 
> type).

Agreed, at least sort of. Qualifiers on parameters and return types are deeply 
weird (IMO): https://godbolt.org/z/zxo3b9WK8

>     * R-value expressions represent an abstract value and are (for the most 
> part) unqualified at the top level. Using a qualified l-value in a context 
> that requires an r-value drops all qualifiers as part of the conversion to an 
> r-value. The qualifiers typically affect the code-generation of the load.

Agreed.

>     * Assignment consumes an abstract value and therefore expects an r-value 
> on the RHS, which (per above) is of unqualified type. The qualifier typically 
> affects code-generation of the store.

Agreed.

>     * The qualifier (if it can apply to structs and unions at all) propagates 
> along member accesses, such that the type of `x.member` has all the top-level 
> qualifiers of both `x` and the member.

Agreed.

> Most of these apply to `_Atomic`. The most notable exception is the last, 
> which C makes UB. But it is not uncommon for qualifiers to have tweaks to 
> various of these rules, like how C++ allows qualified return values of class 
> type for some reason.

Another notable exception is that `_Atomic` is not silently dropped at the top 
level in type for return values and parameters when determining the function 
type, as mentioned above.

> The subtyping rules for CV qualifiers — the ability to e.g. convert an 
> `int**` to `const int * const *` — do not generally work for other 
> qualifiers. For example, even just considering semi-standardized qualifiers, 
> you clearly cannot add an address space in a nested position this way. I 
> believe you have to understand this as a useful special case for the CV 
> qualifiers. (It really should not work for `restrict`, although I can't 
> remember what rule we actually implement.)

Agreed.

> > > So my suggestion is that common functions like getUnqualifiedType() 
> > > should just be looking through _Atomic, which would have fixed this bug 
> > > before it happened. If there's a need for a narrower API that only looks 
> > > through non-_Atomic qualifiers (or perhaps some subset of 
> > > "non-ABI-affecting" qualifiers?), we can add that with a nice name that 
> > > suggests when it should be used.
> > 
> > 
> > On the one hand, this specific bug has bitten us so many times I want to 
> > agree with your proposed approach.
> > On the other hand, that's going to be a massive undertaking that's pretty 
> > risky.
> 
> Yeah, I don't disagree with this, and I'm not trying to force the problem to 
> be solved immediately. I would like to try to get consensus on the right 
> thing to do, though.

Agreed, and I appreciate the conversation on this!

> > We have times when we definitely _do not_ want the qualified type to drop 
> > the atomic qualifier. For example, when merging function types, we call 
> > `getUnqualifiedType()` to determine whether two parameters are compatible. 
> > `void func(const int i);` and `void func(int i);` are compatible 
> > redeclarations, while `void func(_Atomic int i);` and `void func(int i);` 
> > are not. There are other times when we do want the atomic qualifier 
> > dropped. It's a frustrating mess and I'm certain we have numerous bugs in 
> > this area.
> 
> In this case, I have to wonder if the standard is in the same place Clang is 
> — if this was really an intentional allowance for `_Atomic T` as a parameter 
> in function types or just an oversight. It's hard to imagine what an atomic 
> argument could possibly mean. Perhaps it was an oversight that they no longer 
> feel comfortable fixing.

Doesn't it matter in the same way it matters for `const` parameters: it impacts 
the accesses to the parameter within the function body itself? One contrived 
use case is:
```
thrd_t spawn_thread_to_do_things_to(_Atomic int *i);

void func(_Atomic int i) {
  thrd_t t = spawn_thread_to_do_things_to(&i);
  i = 12;
  thrd_join(t, NULL);
}
```

> > So the question to me becomes: do we fix this narrow issue and kick the can 
> > down the road? Or do we try to find someone willing to undergo the more 
> > significant cleanup?
> 
> If we agree that the compiler probably ought to be defaulting to treating 
> atomic types as qualifiers, I think the path is:
> 
>     1. Introduce a function that looks through `_Atomic` as well as the other 
> qualifiers.
> 
>     2. Make a best effort to switch existing call sites of 
> `getUnqualifiedType()` that immediately also look through `_Atomic` to 
> instead use the common function. Probably ought to be the same patch as (1).

I think we've already done this though. We have `getAtomicUnqualifiedType()` 
which we use when we want to get a non-atomic, unqualified type.

>     3. Rework this patch to just use the new function.
> 
>     4. Introduce a function that explicitly does not look through `_Atomic`.

Which is `getUnqualifiedType()` currently.

>     5. Gradually go through the remaining `getUnqualifiedType()` uses and 
> figure out which of the explicit functions they should use, adding tests as 
> necessary.
> 
>     6. When that's done, decide if we've got compelling evidence that 
> `getUnqualifiedType()` should just default to looking through `_Atomic`.
> 
> 
> I think asking Alejandro to do steps 1-3 as part of this bug fix is 
> reasonable, and then the rest can be an open project.

So I think what I'm getting is: we should audit all existing calls to 
`getUnqualifiedType()` and `getAtomicUnqualifiedType()` to determine whether 
we're correctly handling atomics or not, and if there are more uses of 
`getAtomicUnqualifiedType()` after correcting issues, then maybe we want 
`getUnqualifiedType()` to look through atomics and rename 
`getAtomicUnqualifiedType()` to something else?

https://github.com/llvm/llvm-project/pull/119711
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to