On Mon, Jul 06, 2026 at 06:03:26PM +0800, Kito Cheng wrote:
> gcc/ChangeLog:
>
> PR target/124948
> * builtins.cc (get_bitint_atomic_extend_prec): New function.
> (expand_builtin_sync_operation): Use expand_atomic_fetch_op_prec and
> pass the _BitInt extension precision and signedness.
> (expand_builtin_atomic_fetch_op): Likewise, and expand inline even
> with -fno-inline-atomics when the extension is required.
> * optabs.h (expand_atomic_fetch_op_prec): Declare.
> * optabs.cc (expand_atomic_fetch_op_prec): New function, renamed from
> expand_atomic_fetch_op with added PREC and UNSIGNEDP parameters. When
> PREC is nonzero, skip the direct optab, reverse and libcall paths and
> use a compare-and-swap loop that re-extends the new value to PREC bits
> with reduce_rtx_to_bit_field_precision before storing it.
> (expand_atomic_fetch_op): Reimplement as a wrapper passing PREC 0.
> * expr.cc (reduce_rtx_to_bit_field_precision): New function, split out
> of...
> (reduce_to_bit_field_precision): ...here. Call it.
> * expr.h (reduce_rtx_to_bit_field_precision): Declare.
>
> gcc/testsuite/ChangeLog:
>
> PR target/124948
> * gcc.dg/torture/bitint-100.c: New test.
> --- a/gcc/builtins.cc
> +++ b/gcc/builtins.cc
> @@ -6520,6 +6520,37 @@ expand_expr_force_mode (tree exp, machine_mode mode)
> return val;
> }
>
> +/* ADDR is the address argument of an atomic or __sync read-modify-write
> + builtin operating in MODE. If it points to a _BitInt object whose
> precision
> + is narrower than MODE and the target requires the padding bits to stay
> + sign/zero extended (bitint_info::extended is not bitint_ext_undef), return
> + that precision and set *UNSIGNEDP accordingly; otherwise return 0. A
> plain
> + mode-width atomic operation would compute the result over the whole mode
> and
> + leave those padding bits corrupted, so the caller has to keep them
> extended
> + (see expand_atomic_fetch_op_prec). */
> +
> +static unsigned int
> +get_bitint_atomic_extend_prec (tree addr, machine_mode mode, bool *unsignedp)
> +{
> + STRIP_NOPS (addr);
> + scalar_int_mode imode;
> + if (!POINTER_TYPE_P (TREE_TYPE (addr))
> + || TREE_CODE (TREE_TYPE (TREE_TYPE (addr))) != BITINT_TYPE
> + || !is_a <scalar_int_mode> (mode, &imode))
> + return 0;
I'm afraid this approach is incorrect.
The problem is that in GIMPLE pointer casts are useless, so what exact
pointee you get is quite random, and it can be completely unrelated type.
If one e.g. does
void
foo (unsigned _BitInt(17) *p, bool x)
{
if (x)
__atomic_add_fetch (p, 0x0fff0, 0);
else
__atomic_add_fetch ((unsigned long *) p, 0x0fff0, 0);
}
you can get unsigned _BitInt(17) "incorrectly" in both cases, while
void
bar (unsigned long *p, bool x)
{
if (x)
__atomic_add_fetch ((unsigned _BitInt(17) *) p, 0x0fff0, 0);
else
__atomic_add_fetch (p, 0x0fff0, 0);
}
in neither, etc.
IMNSHO, if unsigned _BitInt(17) * pointer makes it to say __atomic_add_fetch_4,
then it should be irrelevant, the required operation is still to load
full 32 bits from the pointer, add another 32 bit value to it and store
full 32 bits.
IMNSHO this is solely about some of the type-generic atomic/sync builtins
(those documented to take TYPE * arguments) and needs to be handled in the
FE, likely in gcc/c-family/c-common.cc (resolve_overloaded_builtin).
There already is code to transform various type-generic builtins into a CAS
loop for say unsigned _BitInt(253), so I think it should be used also for
the case where the type is BITINT_TYPE with any padding bits in the
extend other than bitint_ext_undef mode.
Full list of those builtins is I think below, so one should
check if in each case the behavior for the padding bits is reasonable.
#ifndef N
#define N 17
#endif
#ifdef __SIZEOF_INT128__
#define M 128
#else
#define M 64
#endif
typedef unsigned _BitInt(N) T;
#if N <= M
T f1 (T *p) { return __atomic_load_n (p, __ATOMIC_RELAXED); }
#endif
void f2 (T *p, T *q) { __atomic_load (p, q, __ATOMIC_RELAXED); }
#if N <= M
void f3 (T *p, T q) { __atomic_store_n (p, q, __ATOMIC_RELAXED); }
#endif
void f4 (T *p, T *q) { __atomic_store (p, q, __ATOMIC_RELAXED); }
#if N <= M
T f5 (T *p, T q) { return __atomic_exchange_n (p, q, __ATOMIC_RELAXED); }
#endif
void f6 (T *p, T *q, T *r) { __atomic_exchange (p, q, r, __ATOMIC_RELAXED); }
#if N <= M
bool f7 (T *p, T *q, T r) { return __atomic_compare_exchange_n (p, q, r, false,
__ATOMIC_RELAXED, __ATOMIC_RELAXED); }
bool f8 (T *p, T *q, T *r) { return __atomic_compare_exchange_n (p, q, r,
false, __ATOMIC_RELAXED, __ATOMIC_RELAXED); }
#endif
T f9 (T *p, T q) { return __atomic_add_fetch (p, q, __ATOMIC_RELAXED); }
T f10 (T *p, T q) { return __atomic_sub_fetch (p, q, __ATOMIC_RELAXED); }
T f11 (T *p, T q) { return __atomic_and_fetch (p, q, __ATOMIC_RELAXED); }
T f12 (T *p, T q) { return __atomic_or_fetch (p, q, __ATOMIC_RELAXED); }
T f13 (T *p, T q) { return __atomic_xor_fetch (p, q, __ATOMIC_RELAXED); }
T f14 (T *p, T q) { return __atomic_nand_fetch (p, q, __ATOMIC_RELAXED); }
T f15 (T *p, T q) { return __atomic_fetch_add (p, q, __ATOMIC_RELAXED); }
T f16 (T *p, T q) { return __atomic_fetch_sub (p, q, __ATOMIC_RELAXED); }
T f17 (T *p, T q) { return __atomic_fetch_and (p, q, __ATOMIC_RELAXED); }
T f18 (T *p, T q) { return __atomic_fetch_or (p, q, __ATOMIC_RELAXED); }
T f19 (T *p, T q) { return __atomic_fetch_xor (p, q, __ATOMIC_RELAXED); }
T f20 (T *p, T q) { return __atomic_fetch_nand (p, q, __ATOMIC_RELAXED); }
#if N <= M
T f21 (T *p, T q) { return __sync_add_and_fetch (p, q); }
T f22 (T *p, T q) { return __sync_sub_and_fetch (p, q); }
T f23 (T *p, T q) { return __sync_and_and_fetch (p, q); }
T f24 (T *p, T q) { return __sync_or_and_fetch (p, q); }
T f25 (T *p, T q) { return __sync_xor_and_fetch (p, q); }
T f26 (T *p, T q) { return __sync_nand_and_fetch (p, q); }
T f27 (T *p, T q) { return __sync_fetch_and_add (p, q); }
T f28 (T *p, T q) { return __sync_fetch_and_sub (p, q); }
T f29 (T *p, T q) { return __sync_fetch_and_and (p, q); }
T f30 (T *p, T q) { return __sync_fetch_and_or (p, q); }
T f31 (T *p, T q) { return __sync_fetch_and_xor (p, q); }
T f32 (T *p, T q) { return __sync_fetch_and_nand (p, q); }
bool f33 (T *p, T q, T r) { return __sync_bool_compare_and_swap (p, q, r); }
T f34 (T *p, T q, T r) { return __sync_val_compare_and_swap (p, q, r); }
T f35 (T *p, T q) { return __sync_lock_test_and_set (p, q); }
void f36 (T *p) { __sync_lock_release (p); }
#endif
Jakub