[PATCH 2/2] malloc: introduce ifuncs for malloc functions

DJ Delorie dj@redhat.com
Fri Apr 17 20:37:43 GMT 2026


Yury Khrustalev <yury.khrustalev@arm.com> writes:
> Introduce ifuncs and resolvers for functions pertinent to the
> malloc interface.
>
> In order to do this, we first rename all core implementations by
> adding the '_core' suffix. These functions are supposed to be strictly

Not wanting to bikeshed, but don't we have a history of using _generic
for this purpose?  Or is it your intention that all specific variants
will always call the "core" variants (vs replacing them), and thus a
different suffix is warranted?

> internal and must not be used beyond the malloc code. For example, the
> __libc_malloc() function becomes __libc_malloc_core() and instead
> __libc_malloc() becomes an ifunc with a corresponding resolver that
> may return '__libc_malloc_core' as the implementation.
>
> Each ifunc comes with a generic resolver that will be used unless a
> target overrides the resolvers in its sysdeps directory. All resolvers
> are supposed to be overridden together, and the logic of the generic
> resolvers should be replicated by the target-specific ones.
>
> This patch contains, as an example, aarch64-specific resolvers. At this
> moment they are identical to the generic ones but in the future they can
> be changed to support for features, e.g. to handle memory tagging.
>
> Corresponding aliases are moved next to the ifuncs as well.
>
> On targets that do not support ifuncs we alias externally visible
> functions as well as the __libc_* functions to their respective
> *_core symbols.


> diff --git a/malloc/malloc.c b/malloc/malloc.c
> index 57b58382b1..215feeebb0 100644
> --- a/malloc/malloc.c
> +++ b/malloc/malloc.c
> @@ -216,8 +216,6 @@
>  #include <assert.h>
>  #include <intprops.h>
>  
> -#include <shlib-compat.h>

SHLIB_COMPAT is still used though, in the !HAVE_IFUNC case.

> -void *__libc_malloc (size_t);
> -libc_hidden_proto (__libc_malloc)
> +void *__libc_malloc_core (size_t);
> +libc_hidden_proto (__libc_malloc_core)

Ok.

> -void     __libc_free(void*);
> -libc_hidden_proto (__libc_free)
> +void __libc_free_core (void *);
> +libc_hidden_proto (__libc_free_core)

Arbitrary whitespace changes, but new way is better.  Ok.

>  /*
>    calloc(size_t n_elements, size_t element_size);
>    Returns a pointer to n_elements * element_size bytes, with all locations
>    set to zero.
>  */
> -void*  __libc_calloc(size_t, size_t);
> +void * __libc_calloc_core (size_t, size_t);
> +libc_hidden_proto (__libc_calloc_core)

Ok.

> -void*  __libc_realloc(void*, size_t);
> -libc_hidden_proto (__libc_realloc)
> +void *__libc_realloc_core (void *, size_t);
> +libc_hidden_proto (__libc_realloc_core)

Whitespace again, but ok.

> -void*  __libc_memalign(size_t, size_t);
> -libc_hidden_proto (__libc_memalign)
> +void *__libc_memalign_core (size_t, size_t);
> +libc_hidden_proto (__libc_memalign_core)

Ok.

> -void*  __libc_valloc(size_t);
> -
> +void *__libc_valloc_core (size_t);
> +libc_hidden_proto (__libc_valloc_core)

Ok.

Did we check that the pusblished (non-hidden) symbol list is the same
before and after?

> -void*  __libc_pvalloc(size_t);
> +void *__libc_pvalloc_core (size_t);
> +libc_hidden_proto (__libc_pvalloc_core)

Ok.

> -size_t   __malloc_usable_size(void*);
> +size_t __malloc_usable_size_core (void *);
> +libc_hidden_proto (__malloc_usable_size_core)

Ok.

>    /* We need tcache_key to be non-zero (otherwise tcache_double_free_verify's
>       clearing of e->key would go unnoticed and it would loop getting called
> -     through __libc_free), and we want tcache_key not to be a
> +     through __libc_free_core), and we want tcache_key not to be a
>       commonly-occurring value in memory, so ensure a minimum amount of one and
>       zero bits.  */
>    int minimum_bits = __WORDSIZE / 4;

Ok.

> -  /* Mark this chunk as "in the tcache" so the test in __libc_free will
> +  /* Mark this chunk as "in the tcache" so the test in __libc_free_core will

Ok.

> @@ -3142,7 +3143,7 @@ tcache_double_free_verify (tcache_entry *e)
>       or user data that happens to match the key.  Since we are not sure,
>       clear the key and retry freeing it.  */
>    e->key = 0;
> -  __libc_free (e);
> +  __libc_free_core (e);
>  }

This is only called from within __libc_free_core itself anyway, and only
for the same block, so any ifunc wrapper would have already had its
chance at this chunk.  Thus, not calling the ifunc wrapper again seems
correct to me.

>  void *
> -__libc_malloc (size_t bytes)
> +__libc_malloc_core (size_t bytes)

Ok.

> -libc_hidden_def (__libc_malloc)
> +libc_hidden_def (__libc_malloc_core)

Ok.

>  static void __attribute_noinline__
>  tcache_free_init (void *mem)
>  {
>    tcache_init (NULL);
> -  __libc_free (mem);
> +  __libc_free_core (mem);
>  }

Same logic here.  Ok.

>  void
> -__libc_free (void *mem)
> +__libc_free_core (void *mem)

Ok.

> -libc_hidden_def (__libc_free)
> +libc_hidden_def (__libc_free_core)

Ok.

>  void *
> -__libc_realloc (void *oldmem, size_t bytes)
> +__libc_realloc_core (void *oldmem, size_t bytes)

Ok.

>    /* realloc of null is supposed to be same as malloc */
>    if (oldmem == NULL)
> -    return __libc_malloc (bytes);
> +    return __libc_malloc_core (bytes);

Ok.  We continue to assume that any wrapper will have its chance at the
interface between internal stuff and the user's program.

I wonder, though... we current do tagging operations inside malloc
internals, like splitting chunks.  How will the ifunc interface handle
these?

>  #if REALLOC_ZERO_BYTES_FREES
>    if (bytes == 0)
>      {
> -      __libc_free (oldmem); return NULL;
> +      __libc_free_core (oldmem); return NULL;
>      }
>  #endif

Ok.

>        /* Must alloc, copy, free. */
> -      newmem = __libc_malloc (bytes);
> +      newmem = __libc_malloc_core (bytes);

Ok.

> @@ -3466,7 +3467,7 @@ __libc_realloc (void *oldmem, size_t bytes)
>      {
>        /* Try harder to allocate memory in other arenas.  */
>        LIBC_PROBE (memory_realloc_retry, 2, bytes, oldmem);
> -      newp = __libc_malloc (bytes);
> +      newp = __libc_malloc_core (bytes);

Ok.

> -libc_hidden_def (__libc_realloc)
> +libc_hidden_def (__libc_realloc_core)

Ok.

>  void *
> -__libc_memalign (size_t alignment, size_t bytes)
> +__libc_memalign_core (size_t alignment, size_t bytes)

Ok.

> -libc_hidden_def (__libc_memalign)
> +libc_hidden_def (__libc_memalign_core)

Ok.

>    /* If we need less alignment than we give anyway, just relay to malloc.  */
>    if (alignment <= MALLOC_ALIGNMENT)
> -    return __libc_malloc (bytes);
> +    return __libc_malloc_core (bytes);

Ok.

>  void *
> -__libc_valloc (size_t bytes)
> +__libc_valloc_core (size_t bytes)

Ok.

> +libc_hidden_def (__libc_valloc_core)

Ok.

>  void *
> -__libc_pvalloc (size_t bytes)
> +__libc_pvalloc_core (size_t bytes)

Ok.

> +libc_hidden_def (__libc_pvalloc_core)

Ok.

>  void *
> -__libc_calloc (size_t n, size_t elem_size)
> +__libc_calloc_core (size_t n, size_t elem_size)

Ok.

> +libc_hidden_def (__libc_calloc_core)

Ok.

>  #if IS_IN (libc)
>  size_t
> -__malloc_usable_size (void *m)
> +__malloc_usable_size_core (void *m)

Ok.

> +libc_hidden_def (__malloc_usable_size_core)

Ok.

>  #if IS_IN (libc)
>  weak_alias (__malloc_info, malloc_info)
> -
> -weak_alias (__libc_calloc, calloc)
> -strong_alias (__libc_free, free)
> -strong_alias (__libc_malloc, malloc)
> -weak_alias (__libc_memalign, memalign)
> -strong_alias (__libc_realloc, realloc)
> -weak_alias (__libc_valloc, valloc)
> -weak_alias (__libc_pvalloc, pvalloc)
>  weak_alias (__libc_mallinfo, mallinfo)
>  weak_alias (__libc_mallinfo2, mallinfo2)
>  weak_alias (__libc_mallopt, mallopt)
> -
>  weak_alias (__malloc_stats, malloc_stats)
> -weak_alias (__malloc_usable_size, malloc_usable_size)
>  weak_alias (__malloc_trim, malloc_trim)
> -#endif



> -#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
> -compat_symbol (libc, __libc_free, cfree, GLIBC_2_0);
> -#endif
> +/* On targets that do not support ifuncs we alias externally visible
> +   functions as well as the __libc_* functions to their respective
> +   *_core symbols.
> +
> +   For ifunc prototypes and resolvers see sysdeps/generic/malloc-ifuncs.h.
> + */
> +# if !HAVE_IFUNC
> +strong_alias (__libc_malloc_core, malloc)
> +strong_alias (__libc_malloc_core, __libc_malloc)
> +weak_alias (__libc_calloc_core, calloc)
> +strong_alias (__libc_calloc_core, __libc_calloc)
> +weak_alias (__libc_memalign_core, memalign)
> +strong_alias (__libc_memalign_core, __libc_memalign)
> +weak_alias (__libc_valloc_core, valloc)
> +strong_alias (__libc_valloc_core, __libc_valloc)
> +weak_alias (__libc_pvalloc_core, pvalloc)
> +strong_alias (__libc_pvalloc_core, __libc_pvalloc)
> +strong_alias (__libc_realloc_core, realloc)
> +strong_alias (__libc_realloc_core, __libc_realloc)
> +#  if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
> +compat_symbol (libc, __libc_free_core, cfree, GLIBC_2_0);
> +#  endif
> +strong_alias (__libc_free_core, free)
> +strong_alias (__libc_free_core, __libc_free)
> +weak_alias (__malloc_usable_size_core, malloc_usable_size)
> +# endif /* !HAVE_IFUNC */
> +
> +#endif /* IS_IN (libc) */

Ok.

>  ifeq ($(subdir),malloc)
> +sysdep_routines += \
> +  malloc-ifuncs \
> +  # sysdep_routines

Ok.

> diff --git a/sysdeps/aarch64/malloc-ifuncs.c b/sysdeps/aarch64/malloc-ifuncs.c
> +/* Code for ifunc resolvers for malloc: aarch64 version.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */

Ok.

> +#if IS_IN (libc)

Is there ever a case where we're not in libc?

> +#include <malloc/malloc-internal.h>
> +#include <malloc-ifuncs.h>
> +
> +/* AArch64-specific resolvers for malloc ifuncs.  */
> +
> +IFUNC_PROTO (__libc_malloc);
> +IFUNC_RESOLVER (__libc_malloc, arg0, arg1)

It would be nice if there were some hint as to what these arguments are,
or are used for.  Ifuncs are complicated enough without obfuscating this
information.

> +{
> +  return __libc_malloc_core;
> +}
> +strong_alias (__libc_malloc, malloc)
> +
> +IFUNC_PROTO (__libc_calloc);
> +IFUNC_RESOLVER (__libc_calloc, arg0, arg1)
> +{
> +  return __libc_calloc_core;
> +}
> +weak_alias (__libc_calloc, calloc)
> +
> +IFUNC_PROTO (__libc_memalign);
> +IFUNC_RESOLVER (__libc_memalign, arg0, arg1)
> +{
> +  return __libc_memalign_core;
> +}
> +weak_alias (__libc_memalign, memalign)
> +
> +IFUNC_PROTO (__libc_valloc);
> +IFUNC_RESOLVER (__libc_valloc, arg0, arg1)
> +{
> +  return __libc_valloc_core;
> +}
> +weak_alias (__libc_valloc, valloc)
> +
> +IFUNC_PROTO (__libc_pvalloc);
> +IFUNC_RESOLVER (__libc_pvalloc, arg0, arg1)
> +{
> +  return __libc_pvalloc_core;
> +}
> +weak_alias (__libc_pvalloc, pvalloc)
> +
> +IFUNC_PROTO (__libc_realloc);
> +IFUNC_RESOLVER (__libc_realloc, arg0, arg1)
> +{
> +  return __libc_realloc_core;
> +}
> +strong_alias (__libc_realloc, realloc)
> +
> +IFUNC_PROTO (__libc_free);
> +IFUNC_RESOLVER (__libc_free, arg0, arg1)
> +{
> +  return __libc_free_core;
> +}
> +# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
> +compat_symbol (libc, __libc_free, cfree, GLIBC_2_0);
> +# endif
> +strong_alias (__libc_free, free)
> +
> +IFUNC_PROTO (__malloc_usable_size);
> +IFUNC_RESOLVER (__malloc_usable_size, arg0, arg1)
> +{
> +  return __malloc_usable_size_core;
> +}
> +weak_alias (__malloc_usable_size, malloc_usable_size)
> +
> +#endif /* IS_IN (libc) */

Ok.

> diff --git a/sysdeps/generic/Makefile b/sysdeps/generic/Makefile
>  ifeq ($(subdir),malloc)
> +sysdep_routines += \
> +  malloc-ifuncs \
> +  # sysdep_routines

Ok.

> diff --git a/sysdeps/generic/malloc-ifuncs.c b/sysdeps/generic/malloc-ifuncs.c
> +/* Code for ifunc resolvers for malloc: generic version.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#if IS_IN (libc)

Same here.

> +#include <malloc/malloc-internal.h>
> +#include <malloc-ifuncs.h>
> +
> +# if HAVE_IFUNC
> +
> +/* These resolvers are used by default unless overridden by a target.
> +   The target-specific resolvers must respect this logic if the default
> +   resolvers replicating it where appropriate.

Grammar?  "if..where" sounds like something is missing.

> +   Any aliases for mallo API functions must be defined here as well

typo "mallo"

> +IFUNC_PROTO (__libc_malloc);
> +IFUNC_RESOLVER (__libc_malloc, arg0, arg1)
> +{
> +  return __libc_malloc_core;
> +}
> +strong_alias (__libc_malloc, malloc)
> +
> +IFUNC_PROTO (__libc_calloc);
> +IFUNC_RESOLVER (__libc_calloc, arg0, arg1)
> +{
> +  return __libc_calloc_core;
> +}
> +weak_alias (__libc_calloc, calloc)
> +
> +IFUNC_PROTO (__libc_memalign);
> +IFUNC_RESOLVER (__libc_memalign, arg0, arg1)
> +{
> +  return __libc_memalign_core;
> +}
> +weak_alias (__libc_memalign, memalign)
> +
> +IFUNC_PROTO (__libc_valloc);
> +IFUNC_RESOLVER (__libc_valloc, arg0, arg1)
> +{
> +  return __libc_valloc_core;
> +}
> +weak_alias (__libc_valloc, valloc)
> +
> +IFUNC_PROTO (__libc_pvalloc);
> +IFUNC_RESOLVER (__libc_pvalloc, arg0, arg1)
> +{
> +  return __libc_pvalloc_core;
> +}
> +weak_alias (__libc_pvalloc, pvalloc)
> +
> +IFUNC_PROTO (__libc_realloc);
> +IFUNC_RESOLVER (__libc_realloc, arg0, arg1)
> +{
> +  return __libc_realloc_core;
> +}
> +strong_alias (__libc_realloc, realloc)
> +
> +IFUNC_PROTO (__libc_free);
> +IFUNC_RESOLVER (__libc_free, arg0, arg1)
> +{
> +  return __libc_free_core;
> +}
> +# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
> +compat_symbol (libc, __libc_free, cfree, GLIBC_2_0);
> +# endif
> +strong_alias (__libc_free, free)
> +
> +IFUNC_PROTO (__malloc_usable_size);
> +IFUNC_RESOLVER (__malloc_usable_size, arg0, arg1)
> +{
> +  return __malloc_usable_size_core;
> +}
> +weak_alias (__malloc_usable_size, malloc_usable_size)
> +
> +# endif /* HAVE_IFUNC */
> +
> +#endif /* IS_IN (libc) */

Ok.

> diff --git a/sysdeps/generic/malloc-ifuncs.h b/sysdeps/generic/malloc-ifuncs.h
> +/* Definitions for ifunc resolvers for malloc: generic version.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#ifndef _GENERIC_MALLOC_IFUNCS_H
> +#define _GENERIC_MALLOC_IFUNCS_H
> +
> +#if HAVE_IFUNC
> +
> +#include <stddef.h>
> +#include <sys/cdefs.h>
> +#include <shlib-compat.h>
> +
> +/* Core implementations of malloc functions.  An ifunc resolver must
> +   use this implementations as a fallback option.  Other implementations
> +   may internally call these core function.  */
> +void *__libc_malloc_core (size_t);
> +libc_hidden_proto (__libc_malloc_core)
> +void *__libc_calloc_core (size_t, size_t);
> +libc_hidden_proto (__libc_calloc_core)
> +void *__libc_memalign_core (size_t, size_t);
> +libc_hidden_proto (__libc_memalign_core)
> +void *__libc_valloc_core (size_t);
> +libc_hidden_proto (__libc_valloc_core)
> +void *__libc_pvalloc_core (size_t);
> +libc_hidden_proto (__libc_pvalloc_core)
> +void *__libc_realloc_core (void *, size_t);
> +libc_hidden_proto (__libc_realloc_core)
> +void __libc_free_core (void *);
> +libc_hidden_proto (__libc_free_core)
> +size_t __malloc_usable_size_core (void *);
> +libc_hidden_proto (__malloc_usable_size_core)
> +
> +/* Macros for defining ifunc resolvers for malloc functions.  */
> +#define IFUNC_RESOLVER_NAME(fn) fn ## _resolver
> +#define STR(x) #x
> +#define XSTR(x) STR(x)
> +#define IFUNC_PROTO(fn) \
> +  __typeof (fn ## _core) fn \
> +  __attribute__ ((ifunc (XSTR(IFUNC_RESOLVER_NAME(fn)))))
> +#define IFUNC_RESOLVER(fn, a0, a1) \
> +  static __attribute_used__ \
> +  __typeof (fn ## _core) *IFUNC_RESOLVER_NAME(fn) \
> +  (uint64_t a0, const uint64_t a1[])
> +
> +#endif /* HAVE_IFUNC */
> +
> +#endif /* _GENERIC_MALLOC_IFUNCS_H */

Ok.



More information about the Libc-alpha mailing list