On Sun, Sep 6, 2026 at 10:02 AM Matt Turner <[email protected]> wrote:
>
> On Sun, Sep 6, 2026 at 2:34 AM Richard Biener
> <[email protected]> wrote:
> >
> >
> >
> > > Am 06.09.2026 um 04:59 schrieb Matt Turner <[email protected]>:
> > >
> > > Using a cast as an lvalue is a GCC extension that clang does not
> > > implement. Clang rejects it with -Winvalid-gnu-asm-cast, which is an
> > > error by default, so these blocks fail to build with clang.
> > >
> > > The casts do nothing.
> >
> > That’s not true. The casts serve as typing the macros, with wrong type
> > argument you get a diagnostic.
> >
> > So NACK.
>
> Ah, I hadn't thought about that. I'll try to come up with a way of
> keeping the type safety.
Okay, dug into this a bit more.
For a genuine size mismatch, the cast doesn't give a helpful
diagnostic, it just refuses to compile:
unsigned short s;
__asm__(... : "=r" ((UWtype)s) : ...); // error: lvalue required in
'asm' statement
GCC only accepts the cast-as-lvalue extension when it's a no-op (same
size); anything else is a blanket rejection, not a targeted "wrong
type" diagnostic. And without the cast, the same wrong-size code
frequently compiles clean too, only failing if the assembler happens
to catch a register-size clash in that particular instruction, which
is arch- and instruction-dependent, not something the compiler
guarantees.
More importantly, for same-size type mismatches (e.g. passing a
pointer where UWtype is expected, or wrong signedness), which is the
more realistic bug in these macros, the cast catches nothing at all,
with or without it:
void *p;
__asm__("mov %1,%0" : "=r" (p) : "r"(in)); // compiles, no warning
__asm__("mov %1,%0" : "=r" ((UWtype)p) : "r"(in)); // compiles, no
warning, identical asm
Both produce identical output. So the cast isn't providing the type
safety being claimed; it only accepts or rejects based on operand
size, and even that check is a hard compile error rather than a
diagnostic pointing at the actual problem.
This also matches GMP's own history here: GMP's longlong.h dropped
most of these casts in 1997 (commit 3c47be392, "Don't cast result
operands"), though a stray one on an output operand survives to this
day (the ARM umul_ppmm block), which looks like an oversight.
I'm not sure a replacement type-safety mechanism is practical for this
header specifically. longlong.h is copied and independently maintained
by several other projects (GMP, libgcrypt, and formerly glibc, which
dropped its copy entirely in favor of gmp-arch.h), so it probably
can't depend on anything from gcc/system.h or other GCC-internal
facilities? A portable substitute (e.g. an old-style
negative-array-size sizeof check) would only recover the same
size-only check the cast already gave, which as shown above isn't
catching the mismatches that actually matter, at the cost of macro
clutter at every call site.
Given all that, and that clang rejects the construct outright via
-Winvalid-gnu-asm-cast (an error by default) -- which is a real, live
problem in libgcrypt's own longlong.h today, not a hypothetical -- I
think dropping the casts without a replacement is the right call.
Happy to discuss if there's a different failure mode you've hit that
the cast was actually catching.
If we weren't concerned with compatibility, we could do something like
#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
do { \
_Static_assert (__builtin_types_compatible_p (__typeof__ (sh),
UWtype), "sh must be UWtype"); \
_Static_assert (__builtin_types_compatible_p (__typeof__ (sl),
UWtype), "sl must be UWtype"); \
__asm__ ("addl %5,%1\n\tadcl %3,%0" \
: "=r" (sh), "=&r" (sl) \
: "0" (ah), "g" (bh), "%1" (al), "g" (bl)); \
} while (0)