On Wed, Oct 22, 2025 at 2:22 AM Peter Damianov <[email protected]> wrote:
>
> C23 adds a memalignment function to determine the alignment of a pointer.
> Given how simple it is (convert pointer to an integer then return (p & -p)),
> GCC should support expanding it inline.
>
> This patch implements __builtin_memalignment which expands to p & -p
Does C23 add '__builtin_memalignment' literally? If not, why do we need
an indirection through a builtin and not implement that in terms of p & -p
directly?
> gcc/ChangeLog:
>
> PR middle-end/122117
> * builtin-types.def (BT_FN_SIZE_CONST_PTR): New function type.
> * builtins.def (BUILT_IN_MEMALIGNMENT): New C23 builtin.
> * builtins.cc (fold_builtin_memalignment): New function to fold
> __builtin_memalignment to (size_t)ptr & -(size_t)ptr.
> (fold_builtin_1): Handle BUILT_IN_MEMALIGNMENT.
> (is_inexpensive_builtin): Add BUILT_IN_MEMALIGNMENT.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/builtin-memalignment.c: New test.
> ---
> gcc/builtin-types.def | 1 +
> gcc/builtins.cc | 26 +++++
> gcc/builtins.def | 1 +
> gcc/testsuite/gcc.dg/builtin-memalignment.c | 114 ++++++++++++++++++++
> 4 files changed, 142 insertions(+)
> create mode 100644 gcc/testsuite/gcc.dg/builtin-memalignment.c
>
> diff --git a/gcc/builtin-types.def b/gcc/builtin-types.def
> index 9583d30dfc0..eecb30fd12d 100644
> --- a/gcc/builtin-types.def
> +++ b/gcc/builtin-types.def
> @@ -360,6 +360,7 @@ DEF_FUNCTION_TYPE_1 (BT_FN_LONGLONG_FLOAT32X,
> BT_LONGLONG, BT_FLOAT32X)
> DEF_FUNCTION_TYPE_1 (BT_FN_LONGLONG_FLOAT64X, BT_LONGLONG, BT_FLOAT64X)
> DEF_FUNCTION_TYPE_1 (BT_FN_LONGLONG_FLOAT128X, BT_LONGLONG, BT_FLOAT128X)
> DEF_FUNCTION_TYPE_1 (BT_FN_VOID_PTR, BT_VOID, BT_PTR)
> +DEF_FUNCTION_TYPE_1 (BT_FN_SIZE_CONST_PTR, BT_SIZE, BT_CONST_PTR)
> DEF_FUNCTION_TYPE_1 (BT_FN_SIZE_CONST_STRING, BT_SIZE, BT_CONST_STRING)
> DEF_FUNCTION_TYPE_1 (BT_FN_INT_CONST_STRING, BT_INT, BT_CONST_STRING)
> DEF_FUNCTION_TYPE_1 (BT_FN_PTR_PTR, BT_PTR, BT_PTR)
> diff --git a/gcc/builtins.cc b/gcc/builtins.cc
> index fb294ce58cd..5390b36c0f2 100644
> --- a/gcc/builtins.cc
> +++ b/gcc/builtins.cc
> @@ -172,6 +172,7 @@ static tree fold_builtin_abs (location_t, tree, tree);
> static tree fold_builtin_unordered_cmp (location_t, tree, tree, tree, enum
> tree_code,
> enum tree_code);
> static tree fold_builtin_iseqsig (location_t, tree, tree);
> +static tree fold_builtin_memalignment (location_t, tree);
> static tree fold_builtin_varargs (location_t, tree, tree*, int);
>
> static tree fold_builtin_strpbrk (location_t, tree, tree, tree, tree);
> @@ -10115,6 +10116,27 @@ fold_builtin_iseqsig (location_t loc, tree arg0,
> tree arg1)
> return fold_build2_loc (loc, TRUTH_AND_EXPR, integer_type_node, cmp1,
> cmp2);
> }
>
> +/* Fold a call to __builtin_memalignment(). ARG is the pointer argument.
> + The code is folded to: (size_t)ARG & -(size_t)ARG */
> +
> +static tree
> +fold_builtin_memalignment (location_t loc, tree arg)
> +{
> + tree ptr_as_size, negated, result;
> +
> + /* Convert pointer to size_t. */
> + ptr_as_size = fold_convert_loc (loc, size_type_node, arg);
> +
> + /* Negate: -(size_t)ARG */
> + negated = fold_build1_loc (loc, NEGATE_EXPR, size_type_node, ptr_as_size);
> +
> + /* Compute: (size_t)ARG & -(size_t)ARG */
> + result = fold_build2_loc (loc, BIT_AND_EXPR, size_type_node,
> + ptr_as_size, negated);
> +
> + return result;
> +}
> +
> /* Fold __builtin_{,s,u}{add,sub,mul}{,l,ll}_overflow, either into normal
> arithmetics if it can never overflow, or into internal functions that
> return both result of arithmetics and overflowed boolean flag in
> @@ -10784,6 +10806,9 @@ fold_builtin_1 (location_t loc, tree expr, tree
> fndecl, tree arg0)
> case BUILT_IN_POPCOUNTG:
> return fold_builtin_bit_query (loc, fcode, arg0, NULL_TREE);
>
> + case BUILT_IN_MEMALIGNMENT:
> + return fold_builtin_memalignment (loc, arg0);
> +
> default:
> break;
> }
> @@ -12395,6 +12420,7 @@ is_inexpensive_builtin (tree decl)
> case BUILT_IN_PARITYLL:
> case BUILT_IN_PARITYIMAX:
> case BUILT_IN_PARITY:
> + case BUILT_IN_MEMALIGNMENT:
> case BUILT_IN_LABS:
> case BUILT_IN_LLABS:
> case BUILT_IN_PREFETCH:
> diff --git a/gcc/builtins.def b/gcc/builtins.def
> index 00cfb6a2057..036c79cbd8d 100644
> --- a/gcc/builtins.def
> +++ b/gcc/builtins.def
> @@ -1023,6 +1023,7 @@ DEF_GCC_BUILTIN (BUILT_IN_BSWAP16, "bswap16",
> BT_FN_UINT16_UINT16, ATTR_C
> DEF_GCC_BUILTIN (BUILT_IN_BSWAP32, "bswap32", BT_FN_UINT32_UINT32,
> ATTR_CONST_NOTHROW_LEAF_LIST)
> DEF_GCC_BUILTIN (BUILT_IN_BSWAP64, "bswap64", BT_FN_UINT64_UINT64,
> ATTR_CONST_NOTHROW_LEAF_LIST)
> DEF_GCC_BUILTIN (BUILT_IN_BSWAP128, "bswap128",
> BT_FN_UINT128_UINT128, ATTR_CONST_NOTHROW_LEAF_LIST)
> +DEF_C23_BUILTIN (BUILT_IN_MEMALIGNMENT, "memalignment",
> BT_FN_SIZE_CONST_PTR, ATTR_CONST_NOTHROW_LEAF_LIST)
>
> DEF_EXT_LIB_BUILTIN (BUILT_IN_CLEAR_CACHE, "__clear_cache",
> BT_FN_VOID_PTR_PTR, ATTR_NOTHROW_LEAF_LIST)
> /* [trans-mem]: Adjust BUILT_IN_TM_CALLOC if BUILT_IN_CALLOC is changed. */
> diff --git a/gcc/testsuite/gcc.dg/builtin-memalignment.c
> b/gcc/testsuite/gcc.dg/builtin-memalignment.c
> new file mode 100644
> index 00000000000..993089a52b6
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-memalignment.c
> @@ -0,0 +1,114 @@
> +/* { dg-do run } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +
> +#include <stddef.h>
> +#include <stdint.h>
> +
> +extern void abort (void);
> +
> +void test_alignment (void *ptr, size_t expected)
> +{
> + size_t result = __builtin_memalignment (ptr);
> + if (result != expected)
> + abort ();
> +}
> +
> +/* Verify that constant folding happens */
> +void test_constant_p (void)
> +{
> + /* These should be compile-time constants */
> + if (!__builtin_constant_p (__builtin_memalignment ((void
> *)((uintptr_t)16))))
> + abort ();
> + if (!__builtin_constant_p (__builtin_memalignment ((void
> *)((uintptr_t)24))))
> + abort ();
> + if (!__builtin_constant_p (__builtin_memalignment ((void *)0)))
> + abort ();
> +}
> +
> +int main (void)
> +{
> + /* Test with null pointer - alignment should be 0 */
> + test_alignment ((void *)0, 0);
> +
> + /* Test powers of 2 alignments */
> + test_alignment ((void *)((uintptr_t)1), 1);
> + test_alignment ((void *)((uintptr_t)2), 2);
> + test_alignment ((void *)((uintptr_t)4), 4);
> + test_alignment ((void *)((uintptr_t)8), 8);
> + test_alignment ((void *)((uintptr_t)16), 16);
> + test_alignment ((void *)((uintptr_t)32), 32);
> + test_alignment ((void *)((uintptr_t)64), 64);
> + test_alignment ((void *)((uintptr_t)128), 128);
> + test_alignment ((void *)((uintptr_t)256), 256);
> +
> + /* Test non-power-of-2 addresses */
> + test_alignment ((void *)((uintptr_t)3), 1); /* 0b11 -> alignment 1 */
> + test_alignment ((void *)((uintptr_t)5), 1); /* 0b101 -> alignment 1 */
> + test_alignment ((void *)((uintptr_t)6), 2); /* 0b110 -> alignment 2 */
> + test_alignment ((void *)((uintptr_t)12), 4); /* 0b1100 -> alignment 4 */
> + test_alignment ((void *)((uintptr_t)24), 8); /* 0b11000 -> alignment 8 */
> + test_alignment ((void *)((uintptr_t)48), 16); /* 0b110000 -> alignment 16
> */
> +
> + /* Verify constant folding happens */
> + test_constant_p ();
> +
> + /* Test with variables having different alignments */
> + char c __attribute__((aligned(1)));
> + short s __attribute__((aligned(2)));
> + int i __attribute__((aligned(4)));
> + long long ll __attribute__((aligned(8)));
> + char a16 __attribute__((aligned(16)));
> + char a32 __attribute__((aligned(32)));
> + char a64 __attribute__((aligned(64)));
> +
> + size_t align_c = __builtin_memalignment(&c);
> + size_t align_s = __builtin_memalignment(&s);
> + size_t align_i = __builtin_memalignment(&i);
> + size_t align_ll = __builtin_memalignment(&ll);
> + size_t align_a16 = __builtin_memalignment(&a16);
> + size_t align_a32 = __builtin_memalignment(&a32);
> + size_t align_a64 = __builtin_memalignment(&a64);
> +
> + /* The alignment should be at least what we requested */
> + if (align_c < 1)
> + abort();
> + if (align_s < 2)
> + abort();
> + if (align_i < 4)
> + abort();
> + if (align_ll < 8)
> + abort();
> + if (align_a16 < 16)
> + abort();
> + if (align_a32 < 32)
> + abort();
> + if (align_a64 < 64)
> + abort();
> +
> + /* Test array elements */
> + int array[16] __attribute__((aligned(64)));
> +
> + /* First element should have the array's alignment */
> + if (__builtin_memalignment(&array[0]) < 64)
> + abort();
> +
> + /* Other elements depend on their offset */
> + /* array[1] is at offset 4, so alignment is 4 */
> + if (__builtin_memalignment(&array[1]) != 4)
> + abort();
> +
> + /* array[2] is at offset 8, so alignment is 8 */
> + if (__builtin_memalignment(&array[2]) != 8)
> + abort();
> +
> + /* array[4] is at offset 16, so alignment is 16 */
> + if (__builtin_memalignment(&array[4]) != 16)
> + abort();
> +
> + return 0;
> +}
> +
> +/* { dg-final { scan-tree-dump-not "__builtin_memalignment" "optimized" } }
> */
> +/* { dg-final { scan-tree-dump "return 16;" "optimized" } } */
> +/* { dg-final { scan-tree-dump "return 8;" "optimized" } } */
> +/* { dg-final { scan-tree-dump "return 0;" "optimized" } } */
> --
> 2.47.3
>