[PATCH v2 2/2] elf: Support THP segment load with madvise enabled THP

H.J. Lu hjl.tools@gmail.com
Fri Apr 17 22:52:21 GMT 2026


On Fri, Apr 17, 2026 at 11:50 PM WANG Rui <wangrui@loongson.cn> wrote:
>
> On Fri, Apr 17, 2026 at 2:00 PM H.J. Lu <hjl.tools@gmail.com> wrote:
> >
> > On Fri, Apr 17, 2026 at 12:11 PM H.J. Lu <hjl.tools@gmail.com> wrote:
> > >
> > > The current THP segment load approach works only when THP is enabled
> > > with always in the kernel.  If THP is enabled with madvise in the
> > > kernel, to enable THP segment load in an application, madvise should
> > > be called with MADV_HUGEPAGE on all THP eligible PT_LOAD segments:
> > >
> > > 1. Remove _dl_map_segment_align.  Call _dl_map_segment_adjust to adjust
> > > alignments of PT_LOAD segments, which marks and aligns only THP eligible
> > > PT_LOAD segments to THP page size when loading an object. This fixes
> > > BZ #34079.
> > > 2. Rename DL_MAP_DEFAULT_THP_PAGESIZE to DL_DEFAULT_THP_PAGESIZE.  If
> > > DL_DEFAULT_THP_PAGESIZE is defined, assume that THP is enabled with
> > > madvise in the kernel.  Otherwise, call __get_thp_size to get THP page
> > > size and call __get_thp_mode to get THP mode.
> > > 3. Call _dl_executable_postprocess in rtld_setup_main_map for dynamic
> > > executables and in LIBC_START_MAIN for static executables, which calls
> > > madvise with MADV_HUGEPAGE on all THP eligible PT_LOAD segments in
> > > executable.  This fixes BZ #34080 for both dynamic and static executables.
> > > 4. Call _dl_postprocess_loadcmd_extra in _dl_postprocess_loadcmd, which
> > > calls madvise with MADV_HUGEPAGE on all THP eligible PT_LOAD segments
> > > when loading an object after they have been mapped in.  This fixes
> > > BZ #34080 for shared objects.
> > > 5. Add tests to verify that large executable PT_LOAD segments in
> > > executables and shared libraries are mapped at addresses aligned to THP
> > > page size when the kernel is configured to use THP in "always" mode or
> > > "madvise" mode by inspecting /proc/self/maps to check that the mapping
> > > address is aligned to THP page size reported by the kernel [1].  Also
> > > verify that madvise is called with MADV_HUGEPAGE when the glibc tunable
> > > glibc.elf.thp=1 is used and madvise isn't called with MADV_HUGEPAGE when
> > > the glibc tunable glibc.elf.thp=0 is used.
> > >
> > > Skip these tests if THP page size cannot be determined or if THP is not
> > > enabled in "always" mode nor "madvise" mode.
> > >
> > > Quote WANG Rui <wangrui@loongson.cn>:
> > >
> > > From benchmarking a clang build of the Linux kernel on x86_64 with
> > > your patch in THP madvise mode, I observed that iTLB misses were
> > > reduced, similar to what we see in THP always mode.
> > >
> > > [1] This is developed by WANG Rui <wangrui@loongson.cn>.
> > >
> > > Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
> > > Co-developed-by: WANG Rui <wangrui@loongson.cn>
> >
> > Changes from v1:
> >
> > 1. Rebased against the v10 THP test patch from WANG Rui <wangrui@loongson.cn>.
> > 2. Keep DL_MAP_DEFAULT_THP_PAGESIZE.
> >
> > --
> > H.J.
> > ---
> > The current THP segment load approach works only when THP is enabled
> > with always in the kernel.  If THP is enabled with madvise in the
> > kernel, to enable THP segment load in an application, madvise should
> > be called with MADV_HUGEPAGE on all THP eligible PT_LOAD segments:
> >
> > 1. Remove _dl_map_segment_align.  Call _dl_map_segment_adjust to adjust
> > alignments of PT_LOAD segments, which marks and aligns only THP eligible
> > PT_LOAD segments to THP page size when loading an object. This fixes
> > BZ #34079.
> > 2. Call _dl_executable_postprocess in rtld_setup_main_map for dynamic
> > executables and in LIBC_START_MAIN for static executables, which calls
> > madvise with MADV_HUGEPAGE on all THP eligible PT_LOAD segments in
> > executable.  This fixes BZ #34080 for both dynamic and static executables.
> > 3. Call _dl_postprocess_loadcmd_extra in _dl_postprocess_loadcmd, which
> > calls madvise with MADV_HUGEPAGE on all THP eligible PT_LOAD segments
> > when loading an object after they have been mapped in.  This fixes
> > BZ #34080 for shared objects.
> > 4. Add tests to verify that large executable PT_LOAD segments in
> > executables and shared libraries are mapped at addresses aligned to THP
> > page size when the kernel is configured to use THP in "always" mode or
> > "madvise" mode by inspecting /proc/self/maps to check that the mapping
> > address is aligned to THP page size reported by the kernel [1].  Also
> > verify that madvise is called with MADV_HUGEPAGE when the glibc tunable
> > glibc.elf.thp=1 is used and madvise isn't called with MADV_HUGEPAGE when
> > the glibc tunable glibc.elf.thp=0 is used.
> >
> > Skip these tests if THP page size cannot be determined or if THP is not
> > enabled in "always" mode nor "madvise" mode.
> >
> > Quote WANG Rui <wangrui@loongson.cn>:
> >
> > From benchmarking a clang build of the Linux kernel on x86_64 with
> > your patch in THP madvise mode, I observed that iTLB misses were
> > reduced, similar to what we see in THP always mode.
> >
> > [1] This is developed by WANG Rui <wangrui@loongson.cn>.
> >
> > Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
> > Co-developed-by: WANG Rui <wangrui@loongson.cn>
> > ---
> >  csu/libc-start.c                              |   4 +
> >  elf/dl-load.c                                 |  10 +-
> >  elf/dl-load.h                                 |   5 +
> >  elf/dl-support.c                              |   6 +
> >  elf/rtld.c                                    |  17 +--
> >  sysdeps/generic/dl-exec-post.h                |  34 +++++
> >  ...{dl-map-segment-align.h => dl-load-post.h} |   7 +-
> >  sysdeps/generic/dl-map-segment-adjust.h       |  30 ++++
> >  sysdeps/generic/hugepages.h                   |   4 +-
> >  sysdeps/generic/ldsodefs.h                    |  11 ++
> >  sysdeps/unix/sysv/linux/Makefile              | 124 +++++++++++++++-
> >  sysdeps/unix/sysv/linux/dl-exec-post.h        |  95 +++++++++++++
> >  sysdeps/unix/sysv/linux/dl-load-post.h        |  32 +++++
> >  .../unix/sysv/linux/dl-map-segment-adjust.c   |  67 +++++++++
> >  ...egment-align.h => dl-map-segment-adjust.h} |  10 +-
> >  .../unix/sysv/linux/dl-map-segment-align.c    |  55 --------
> >  sysdeps/unix/sysv/linux/ldsodefs.h            |   3 +
> >  sysdeps/unix/sysv/linux/loongarch/Makefile    |   3 +
> >  ...{dl-map-segment-align.h => dl-exec-post.h} |   4 +-
> >  sysdeps/unix/sysv/linux/strace-tst-thp.sh     |  56 ++++++++
> >  .../unix/sysv/linux/tst-thp-1-no-s-code-pde.c |  19 +++
> >  .../sysv/linux/tst-thp-1-no-s-code-static.c   |  19 +++
> >  sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c |  19 +++
> >  sysdeps/unix/sysv/linux/tst-thp-1-pde.c       |  19 +++
> >  sysdeps/unix/sysv/linux/tst-thp-1-static.c    |  19 +++
> >  sysdeps/unix/sysv/linux/tst-thp-1.c           |  28 ++++
> >  sysdeps/unix/sysv/linux/tst-thp-align-check.h | 132 ++++++++++++++++++
> >  sysdeps/unix/sysv/linux/tst-thp-align.c       |  36 +++++
> >  sysdeps/unix/sysv/linux/tst-thp-size-mod.S    |  24 ++++
> >  sysdeps/unix/sysv/linux/x86/dl-exec-post.h    |  22 +++
> >  30 files changed, 829 insertions(+), 85 deletions(-)
> >  create mode 100644 sysdeps/generic/dl-exec-post.h
> >  rename sysdeps/generic/{dl-map-segment-align.h => dl-load-post.h} (83%)
> >  create mode 100644 sysdeps/generic/dl-map-segment-adjust.h
> >  create mode 100644 sysdeps/unix/sysv/linux/dl-exec-post.h
> >  create mode 100644 sysdeps/unix/sysv/linux/dl-load-post.h
> >  create mode 100644 sysdeps/unix/sysv/linux/dl-map-segment-adjust.c
> >  rename sysdeps/unix/sysv/linux/{dl-map-segment-align.h => dl-map-segment-adjust.h} (79%)
> >  delete mode 100644 sysdeps/unix/sysv/linux/dl-map-segment-align.c
> >  rename sysdeps/unix/sysv/linux/loongarch/lp64/{dl-map-segment-align.h => dl-exec-post.h} (90%)
> >  create mode 100644 sysdeps/unix/sysv/linux/strace-tst-thp.sh
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-pde.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-static.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-align-check.h
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-align.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-size-mod.S
> >  create mode 100644 sysdeps/unix/sysv/linux/x86/dl-exec-post.h
> >
> > diff --git a/csu/libc-start.c b/csu/libc-start.c
> > index 1c58561bce..be60831a42 100644
> > --- a/csu/libc-start.c
> > +++ b/csu/libc-start.c
> > @@ -205,6 +205,7 @@ call_fini (void *unused)
> >  #endif /* !SHARED */
> >
> >  #include <libc-start.h>
> > +#include <dl-exec-post.h>
> >
> >  STATIC int LIBC_START_MAIN (int (*main) (int, char **, char **
> >  MAIN_AUXVEC_DECL),
> > @@ -305,6 +306,9 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
> >    __pointer_chk_guard_local = pointer_chk_guard;
> >  # endif
> >
> > +  struct link_map *main_map = _dl_get_dl_main_map ();
> > +  _dl_executable_postprocess (main_map, GL(dl_phdr), GL(dl_phnum));
> > +
> >  #endif /* !SHARED  */
> >
> >    /* Register the destructor of the dynamic linker if there is any.  */
> > diff --git a/elf/dl-load.c b/elf/dl-load.c
> > index 48575fff06..800b7ca1a6 100644
> > --- a/elf/dl-load.c
> > +++ b/elf/dl-load.c
> > @@ -71,7 +71,7 @@ struct filebuf
> >  #include <dl-dst.h>
> >  #include <dl-load.h>
> >  #include <dl-map-segments.h>
> > -#include <dl-map-segment-align.h>
> > +#include <dl-map-segment-adjust.h>
> >  #include <dl-unmap-segments.h>
> >  #include <dl-machine-reject-phdr.h>
> >  #include <dl-prop.h>
> > @@ -1173,8 +1173,8 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
> >    /* Optimize a common case.  */
> >    c->prot = pf_to_prot (ph->p_flags);
> >
> > -   /* Architecture-specific adjustment of segment alignment. */
> > -   p_align_max = _dl_map_segment_align (c, p_align_max);
> > +   /* Initialize flags.  */
> > +   c->flags = 0;
> >    break;
> >
> >  case PT_TLS:
> > @@ -1229,9 +1229,7 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
> >  goto lose;
> >        }
> >
> > -    /* Align all PT_LOAD segments to the maximum p_align.  */
> > -    for (size_t i = 0; i < nloadcmds; i++)
> > -      loadcmds[i].mapalign = p_align_max;
> > +    _dl_map_segment_adjust (loadcmds, &nloadcmds, p_align_max);
> >
> >      /* dlopen of an executable is not valid because it is not possible
> >         to perform proper relocations, handle static TLS, or run the
> > diff --git a/elf/dl-load.h b/elf/dl-load.h
> > index 897c4034c5..55edf0323c 100644
> > --- a/elf/dl-load.h
> > +++ b/elf/dl-load.h
> > @@ -78,8 +78,11 @@ struct loadcmd
> >    ElfW(Addr) mapstart, mapend, dataend, allocend, mapalign;
> >    ElfW(Off) mapoff;
> >    int prot;                             /* PROT_* bits.  */
> > +  /* May be used by _dl_postprocess_loadcmd_extra.  */
> > +  unsigned int flags;
> >  };
> >
> > +#include <dl-load-post.h>
> >
> >  /* This is a subroutine of _dl_map_segments.  It should be called for each
> >     load command, some time after L->l_addr has been set correctly.  It is
> > @@ -95,6 +98,8 @@ _dl_postprocess_loadcmd (struct link_map *l, const ElfW(Ehdr) *header,
> >      /* Found the program header in this segment.  */
> >      l->l_phdr = (void *) (uintptr_t) (c->mapstart + header->e_phoff
> >                                        - c->mapoff);
> > +
> > +  _dl_postprocess_loadcmd_extra (l, c);
> >  }
> >
> >
> > diff --git a/elf/dl-support.c b/elf/dl-support.c
> > index 0508d6113b..420e418b40 100644
> > --- a/elf/dl-support.c
> > +++ b/elf/dl-support.c
> > @@ -179,6 +179,12 @@ int _dl_stack_cache_lock;
> >  #endif
> >  struct dl_scope_free_list *_dl_scope_free_list;
> >
> > +#ifdef HAVE_THP
> > +int _dl_thp_control = -1;
> > +enum thp_mode_t _dl_thp_mode;
> > +size_t _dl_thp_pagesize;
> > +#endif
> > +
> >  #ifdef NEED_DL_SYSINFO
> >  /* Needed for improved syscall handling on at least x86/Linux.  NB: Don't
> >     initialize it here to avoid RELATIVE relocation in static PIE.  */
> > diff --git a/elf/rtld.c b/elf/rtld.c
> > index e926ec73e4..fe8d6f87e3 100644
> > --- a/elf/rtld.c
> > +++ b/elf/rtld.c
> > @@ -52,6 +52,7 @@
> >  #include <dl-find_object.h>
> >  #include <dl-audit-check.h>
> >  #include <dl-call_tls_init_tp.h>
> > +#include <dl-exec-post.h>
> >
> >  #include <assert.h>
> >
> > @@ -323,6 +324,9 @@ struct rtld_global _rtld_global =
> >      /* Generally the default presumption without further information is an
> >       * executable stack but this is not true for all platforms.  */
> >      ._dl_stack_prot_flags = DEFAULT_STACK_PROT_PERMS,
> > +#ifdef HAVE_THP
> > +    ._dl_thp_control = -1,
> > +#endif
> >  #ifdef _LIBC_REENTRANT
> >      ._dl_load_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
> >      ._dl_load_write_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
> > @@ -1209,14 +1213,8 @@ rtld_setup_main_map (struct link_map *main_map)
> >  main_map->l_relro_size = ph->p_memsz;
> >  break;
> >        }
> > -  /* Process program headers again, but scan them backwards since
> > -     PT_GNU_PROPERTY is close to the end of program headers.   */
> > -  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
> > -    if (ph[-1].p_type == PT_GNU_PROPERTY)
> > -      {
> > - _dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
> > - break;
> > -      }
> > +
> > +  _dl_executable_postprocess (main_map, phdr, phnum);
> >
> >    /* Adjust the address of the TLS initialization image in case
> >       the executable is actually an ET_DYN object.  */
> > @@ -1589,6 +1587,9 @@ dl_main (const ElfW(Phdr) *phdr,
> >  {
> >    RTLD_TIMING_VAR (start);
> >    rtld_timer_start (&start);
> > +#ifdef HAVE_THP
> > +   _dl_get_thp_config ();
> > +#endif
> >    _dl_map_object (NULL, rtld_progname, lt_executable, 0,
> >    __RTLD_OPENEXEC, LM_ID_BASE);
> >    rtld_timer_stop (&load_time, start);
> > diff --git a/sysdeps/generic/dl-exec-post.h b/sysdeps/generic/dl-exec-post.h
> > new file mode 100644
> > index 0000000000..f5dcdc093a
> > --- /dev/null
> > +++ b/sysdeps/generic/dl-exec-post.h
> > @@ -0,0 +1,34 @@
> > +/* _dl_executable_postprocess.  Generic version.
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +   Copyright The GNU Toolchain Authors.
> > +   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/>.  */
> > +
> > +static inline void
> > +_dl_executable_postprocess (struct link_map *main_map,
> > +     const ElfW(Phdr) *phdr, ElfW(Word) phnum)
> > +{
> > +#ifdef SHARED
> > +  /* Process program headers again, but scan them backwards since
> > +     PT_GNU_PROPERTY is close to the end of program headers.   */
> > +  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
> > +    if (ph[-1].p_type == PT_GNU_PROPERTY)
> > +      {
> > + _dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
> > + break;
> > +      }
> > +#endif
> > +}
> > diff --git a/sysdeps/generic/dl-map-segment-align.h b/sysdeps/generic/dl-load-post.h
> > similarity index 83%
> > rename from sysdeps/generic/dl-map-segment-align.h
> > rename to sysdeps/generic/dl-load-post.h
> > index f4a671f25f..bfaad8ea7c 100644
> > --- a/sysdeps/generic/dl-map-segment-align.h
> > +++ b/sysdeps/generic/dl-load-post.h
> > @@ -1,4 +1,4 @@
> > -/* _dl_map_segment_align.  Generic version.
> > +/* _dl_postprocess_loadcmd_extra.  Generic version.
> >     Copyright (C) 2026 Free Software Foundation, Inc.
> >     Copyright The GNU Toolchain Authors.
> >     This file is part of the GNU C Library.
> > @@ -19,8 +19,7 @@
> >
> >  #include <dl-load.h>
> >
> > -static inline ElfW(Addr)
> > -_dl_map_segment_align (const struct loadcmd *c, ElfW(Addr) p_align_max)
> > +static inline void
> > +_dl_postprocess_loadcmd_extra (struct link_map *l, const struct loadcmd *c)
> >  {
> > -  return p_align_max;
> >  }
> > diff --git a/sysdeps/generic/dl-map-segment-adjust.h b/sysdeps/generic/dl-map-segment-adjust.h
> > new file mode 100644
> > index 0000000000..b48057ec26
> > --- /dev/null
> > +++ b/sysdeps/generic/dl-map-segment-adjust.h
> > @@ -0,0 +1,30 @@
> > +/* _dl_map_segment_adjust.  Generic version.
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +   Copyright The GNU Toolchain Authors.
> > +   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/>.  */
> > +
> > +#include <dl-load.h>
> > +
> > +static inline void
> > +_dl_map_segment_adjust (struct loadcmd *load_cmds,
> > + size_t *n_load_cmds, ElfW(Addr) p_align_max)
> > +{
> > +  /* Align all PT_LOAD segments to the maximum p_align.  */
> > +  size_t n = *n_load_cmds;
> > +  for (size_t i = 0; i < n; i++)
> > +    load_cmds[i].mapalign = p_align_max;
> > +}
> > diff --git a/sysdeps/generic/hugepages.h b/sysdeps/generic/hugepages.h
> > index 5fc9b5c8de..df9ec4920b 100644
> > --- a/sysdeps/generic/hugepages.h
> > +++ b/sysdeps/generic/hugepages.h
> > @@ -26,10 +26,10 @@ unsigned long int __get_thp_size (void) attribute_hidden;
> >
> >  enum thp_mode_t
> >  {
> > +  thp_mode_not_supported = 0,
> >    thp_mode_always,
> >    thp_mode_madvise,
> > -  thp_mode_never,
> > -  thp_mode_not_supported
> > +  thp_mode_never
> >  };
> >
> >  enum thp_mode_t __get_thp_mode (void) attribute_hidden;
> > diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
> > index 15c4659853..36f2e88ae4 100644
> > --- a/sysdeps/generic/ldsodefs.h
> > +++ b/sysdeps/generic/ldsodefs.h
> > @@ -39,6 +39,7 @@
> >  #include <libc-lock.h>
> >  #include <hp-timing.h>
> >  #include <list_t.h>
> > +#include <hugepages.h>
> >
> >  __BEGIN_DECLS
> >
> > @@ -477,6 +478,16 @@ struct rtld_global
> >    EXTERN struct __pthread **_dl_pthread_threads;
> >    __mach_rwlock_define (EXTERN, _dl_pthread_threads_lock)
> >  #endif
> > +#ifdef HAVE_THP
> > +  /* The THP segment load control:
> > +      > 0: Enabled by GLIBC_TUNABLES=glibc.elf.thp=1.
> > +        0: Disabled by GLIBC_TUNABLES=glibc.elf.thp=0.
> > +      < 0: To be enabled or disabled by GLIBC_TUNABLES.  */
>
> I think we need to treat THP-always and THP-madvise differently. For
> always, it seems reasonable to let an architecture enable
> `glibc.elf.thp` by default. But for madvise, it should be opt-in only.

When DL_MAP_DEFAULT_THP_PAGESIZE is defined, we assume
THP is enabled with madvise conversatively  I will change
 _dl_get_thp_config to

  GL(dl_thp_pagesize) = __get_thp_size ();
  GL(dl_thp_mode) = __get_thp_mode ();
  /* We cap the huge page size at MAX_THP_PAGESIZE to avoid over-aligning
     on systems with very large normal pages (like 64K pages with 512M
     huge pages).  */
  if (GL(dl_thp_pagesize) > MAX_THP_PAGESIZE)
    GL(dl_thp_control) = 0;
  /* NB: Enable THP if THP is always enabled in the kernel. */
  else if (GL(dl_thp_mode) == thp_mode_always)
    GL(dl_thp_control) = 1;

> > +  EXTERN int _dl_thp_control;
> > +
> > +  EXTERN enum thp_mode_t _dl_thp_mode;
> > +  EXTERN size_t _dl_thp_pagesize;
> > +#endif
> >  #ifdef SHARED
> >  };
> >  # define __rtld_global_attribute__
> > diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
> > index b06afbdeaa..41d321c564 100644
> > --- a/sysdeps/unix/sysv/linux/Makefile
> > +++ b/sysdeps/unix/sysv/linux/Makefile
> > @@ -673,7 +673,7 @@ endif
> >
> >  ifeq ($(subdir),elf)
> >  dl-routines += \
> > -  dl-map-segment-align \
> > +  dl-map-segment-adjust \
> >    dl-rseq-symbols \
> >    # dl-routines
> >
> > @@ -698,13 +698,19 @@ $(objpfx)pldd: $(objpfx)xmalloc.o
> >  tests += \
> >    tst-rseq-tls-range \
> >    tst-rseq-tls-range-4096 \
> > +  tst-thp-1 \
> > +  tst-thp-1-pde \
> > +  tst-thp-1-static \
> > +  tst-thp-align \
> >  # tests
> >  tests-static += \
> >    tst-rseq-tls-range-4096-static \
> >    tst-rseq-tls-range-static \
> > +  tst-thp-1-static \
> >  # tests-static
> >  modules-names += \
> >    tst-rseq-tls-range-mod \
> > +  tst-thp-size-mod \
> >  # modules-names
> >  CFLAGS-tst-rseq-tls-range.c += -DMAIN_TLS_ALIGN=4
> >  CFLAGS-tst-rseq-tls-range-4096.c += -DMAIN_TLS_ALIGN=4096
> > @@ -724,6 +730,122 @@ tests-special += \
> >    $(objpfx)tst-nolink-libc-2.out \
> >    # tests-special
> >  endif
> > +
> > +ifndef THP-PAGE-SIZE
> > +# Align PT_LOAD segments in THP tests to THP page size so that kernel will
> > +# map PIE to the address aligned to THP page size.  Default THP page size
> > +# to 2MB which can be overridden in Makefile in subdirectories.
> > +THP-PAGE-SIZE = 0x200000
> > +endif
> > +
> > +THP-PAGE-SIZE-LDFLAGS = -Wl,-z,max-page-size=$(THP-PAGE-SIZE)
> > +
> > +LDFLAGS-tst-thp-size-mod.so = $(THP-PAGE-SIZE-LDFLAGS)
> > +tst-thp-align-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +$(objpfx)tst-thp-align.out: $(objpfx)tst-thp-size-mod.so
> > +
> > +ifeq ($(have-separate-code),yes)
> > +tests += \
> > +  tst-thp-1-no-s-code \
> > +  tst-thp-1-no-s-code-pde \
> > +  tst-thp-1-no-s-code-static \
> > +# tests
> > +tests-static += \
> > +  tst-thp-1-no-s-code-static \
> > +# tests-static
> > +
> > +LDFLAGS-tst-thp-1 = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS)
> > +LDFLAGS-tst-thp-1-pde = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS)
> > +LDFLAGS-tst-thp-1-static = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS)
> > +LDFLAGS-tst-thp-1-no-s-code = -Wl,-z,noseparate-code \
> > +       $(THP-PAGE-SIZE-LDFLAGS)
> > +LDFLAGS-tst-thp-1-no-s-code-pde = -Wl,-z,noseparate-code \
> > +   $(THP-PAGE-SIZE-LDFLAGS)
> > +LDFLAGS-tst-thp-1-no-s-code-static = -Wl,-z,noseparate-code \
> > +      $(THP-PAGE-SIZE-LDFLAGS)
> > +
> > +$(objpfx)tst-thp-1-no-s-code: $(objpfx)tst-thp-size-mod.o
> > +$(objpfx)tst-thp-1-no-s-code-pde: $(objpfx)tst-thp-size-mod.o
> > +$(objpfx)tst-thp-1-no-s-code-static: $(objpfx)tst-thp-size-mod.o
> > +
> > +tst-thp-1-no-s-code-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +tst-thp-1-no-s-code-pde-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +tst-thp-1-no-s-code-static-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +
> > +tst-thp-1-no-s-code-pde-no-pie = yes
> > +endif
> > +
> > +tst-thp-1-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +tst-thp-1-pde-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +tst-thp-1-static-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +
> > +$(objpfx)tst-thp-1: $(objpfx)tst-thp-size-mod.o
> > +$(objpfx)tst-thp-1-pde: $(objpfx)tst-thp-size-mod.o
> > +$(objpfx)tst-thp-1-static: $(objpfx)tst-thp-size-mod.o
> > +
> > +tst-thp-1-pde-no-pie = yes
> > +
> > +thp-kernel-status = $(shell grep madvise /sys/kernel/mm/transparent_hugepage/enabled)
> > +# Verify that madvise is called with MADV_HUGEPAGE when THP is enabled
> > +# under madvise THP kernel.
> > +ifneq ($(findstring [madvise],$(thp-kernel-status)),)
> > +tests-special += \
> > +  $(objpfx)strace-tst-thp-1-disabled.out \
> > +  $(objpfx)strace-tst-thp-1-enabled.out \
> > +  $(objpfx)strace-tst-thp-1-pde-disabled.out \
> > +  $(objpfx)strace-tst-thp-1-pde-enabled.out \
> > +  $(objpfx)strace-tst-thp-1-static-disabled.out \
> > +  $(objpfx)strace-tst-thp-1-static-enabled.out \
> > +# tests-special
> > +
> > +$(objpfx)strace-tst-thp-1-enabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-1
> > + $(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > + '$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
> > + '$(rpath-link)' $(objpfx)tst-thp-1 > $@;
> > +   $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-1-disabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-1
> > + $(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > + '$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
> > + '$(rpath-link)' $(objpfx)tst-thp-1 > $@;
> > +   $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-1-pde-enabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-1-pde
> > + $(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > + '$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
> > + '$(rpath-link)' $(objpfx)tst-thp-1-pde > $@; \
> > +   $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-1-pde-disabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-1-pde
> > + $(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > + '$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
> > + '$(rpath-link)' $(objpfx)tst-thp-1-pde > $@; \
> > +   $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-1-static-enabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-1-static
> > + $(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > + '$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
> > + '$(rpath-link)' $(objpfx)tst-thp-1-static > $@; \
> > +   $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-1-static-disabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-1-static
> > + $(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > + '$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
> > + '$(rpath-link)' $(objpfx)tst-thp-1-static > $@; \
> > +   $(evaluate-test)
> > +endif
> >  endif # $(subdir) == elf
> >
> >  ifeq ($(subdir),rt)
> > diff --git a/sysdeps/unix/sysv/linux/dl-exec-post.h b/sysdeps/unix/sysv/linux/dl-exec-post.h
> > new file mode 100644
> > index 0000000000..1ef1f6556e
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/dl-exec-post.h
> > @@ -0,0 +1,95 @@
> > +/* _dl_executable_postprocess.  Linux version.
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +   Copyright The GNU Toolchain Authors.
> > +   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/>.  */
> > +
> > +static inline void
> > +_dl_get_thp_config (void)
> > +{
> > +  /* Check if there is GLIBC_TUNABLES=glibc.elf.thp=[0|1].  */
> > +  if (TUNABLE_IS_INITIALIZED_FULL (glibc, elf, thp))
> > +    GL(dl_thp_control) = TUNABLE_GET_FULL (glibc, elf, thp, int32_t,
> > +    NULL);
> > +
> > +  /* Return if THP is disabled by GLIBC_TUNABLES=glibc.elf.thp=0.  */
> > +  if (GL(dl_thp_control) == 0)
> > +    return;
> > +
> > +#ifdef DL_MAP_DEFAULT_THP_PAGESIZE
> > +  /* NB: Accessing /sys/kernel/mm files is quite expensive and the file
> > +     may not be accessible in containers.  If DL_MAP_DEFAULT_THP_PAGESIZE
> > +     is defined, assume THP mode is madvise and always call madvise.
> > +     Since madvise is a fast systemcall, it adds only a small overhead
> > +     compared to the cost of accessing /sys/kernel/mm files.  */
> > +  GL(dl_thp_pagesize) = DL_MAP_DEFAULT_THP_PAGESIZE;
> > +  GL(dl_thp_mode) = thp_mode_madvise;
> > +#else
> > +  GL(dl_thp_pagesize) = __get_thp_size ();
> > +  GL(dl_thp_mode) = __get_thp_mode ();
> > +#endif
> > +}
> > +
> > +static inline void
> > +_dl_executable_postprocess (struct link_map *main_map,
> > +     const ElfW(Phdr) *phdr, ElfW(Word) phnum)
> > +{
> > +  if (GL(dl_thp_control) == -1)
> > +    _dl_get_thp_config ();
> > +
> > +  /* NB: In static executable, PT_GNU_PROPERTY is processed in target
> > +     libc-start.h if it is needed by target.  When ld.so is used, if
> > +     a target doesn't need PT_GNU_PROPERTY, _dl_process_pt_gnu_property
> > +     is an empty function.  */
> > +#ifdef SHARED
> > +  /* Process program headers again, but scan them backwards since
> > +     PT_GNU_PROPERTY is close to the end of program headers.   */
> > +  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
> > +    if (ph[-1].p_type == PT_GNU_PROPERTY)
> > +      {
> > + _dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
> > + break;
> > +      }
> > +#endif
> > +
> > +  /* Return if THP segment load isn't enabled.  */
> > +  if (GL(dl_thp_control) <= 0)
> > +    return;
> > +
> > +#ifndef DL_MAP_DEFAULT_THP_PAGESIZE
> > +  /* NB: If DL_MAP_DEFAULT_THP_PAGESIZE is defined, dl_thp_mode is always
> > +     set to thp_mode_madvise.  */
> > +  if (GL(dl_thp_mode) != thp_mode_madvise)
> > +    return;
> > +#endif
> > +
> > +  /* When we get here, the main executable have been mapped in.  Call
> > +     madvise with MADV_HUGEPAGE for all THP eligible PT_LOAD segments.  */
> > +
> > +  const ElfW(Phdr) *ph;
> > +
> > +  size_t thp_pagesize = GL(dl_thp_pagesize);
> > +
> > +  /* Call __madvise if offset and address of the PT_LOAD segment are
> > +     aligned to THP page size and it is read-only.  */
> > +  for (ph = phdr; ph < &phdr[phnum]; ++ph)
> > +    if (ph->p_type == PT_LOAD
> > + && ph->p_memsz >= thp_pagesize
> > + && ((ph->p_vaddr | ph->p_offset) & (thp_pagesize - 1)) == 0
> > + && (ph->p_flags & (PF_W | PF_R)) == PF_R)
> > +      __madvise ((void *) (main_map->l_addr + ph->p_vaddr),
> > + ph->p_memsz, MADV_HUGEPAGE);
> > +}
> > diff --git a/sysdeps/unix/sysv/linux/dl-load-post.h b/sysdeps/unix/sysv/linux/dl-load-post.h
> > new file mode 100644
> > index 0000000000..b3c4e16bb7
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/dl-load-post.h
> > @@ -0,0 +1,32 @@
> > +/* _dl_postprocess_loadcmd_extra.  Linux version.
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +   Copyright The GNU Toolchain Authors.
> > +   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/>.  */
> > +
> > +/* Bits in the flags field of struct loadcmd.  */
> > +#define LOADCMD_POST_MADVISE_THP (0x1 << 0)
> > +
> > +/* After L has been mapped in, call madvise with MADV_HUGEPAGE if L is
> > +   THP eligible.  */
> > +
> > +static inline void
> > +_dl_postprocess_loadcmd_extra (struct link_map *l, const struct loadcmd *c)
> > +{
> > +  if ((c->flags & LOADCMD_POST_MADVISE_THP) != 0)
> > +    __madvise ((void *) (l->l_addr + c->mapstart),
> > +        c->mapend - c->mapstart, MADV_HUGEPAGE);
> > +}
> > diff --git a/sysdeps/unix/sysv/linux/dl-map-segment-adjust.c b/sysdeps/unix/sysv/linux/dl-map-segment-adjust.c
> > new file mode 100644
> > index 0000000000..90c8ea92fe
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/dl-map-segment-adjust.c
> > @@ -0,0 +1,67 @@
> > +/* _dl_map_segment_adjust.  Linux version.
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +   Copyright The GNU Toolchain Authors.
> > +   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/>.  */
> > +
> > +#include <ldsodefs.h>
> > +#include <dl-map-segment-adjust.h>
> > +
> > +/* Set the mapalign field in entries in LOAD_CMDS to align PT_LOAD
> > +   segments for THP.  P_ALIGN_MAX is the maximum p_align value in all
> > +   PT_LOAD segments.  */
> > +
> > +void
> > +_dl_map_segment_adjust (struct loadcmd *load_cmds, size_t *n_load_cmds,
> > + ElfW(Addr) p_align_max)
>
> size_t n_load_cmds?

Fixed.

> > +{
> > +  size_t n = *n_load_cmds;
> > +  size_t i;
> > +  enum thp_mode_t thp_mode = GL(dl_thp_mode);
> > +  size_t thp_pagesize = GL(dl_thp_pagesize);
> > +  struct loadcmd *c = load_cmds;
> > +
> > +  if (n == 0)
> > +    return;
> > +
> > +  /* We cap the huge page size at MAX_THP_PAGESIZE to avoid over-aligning
> > +     on systems with very large normal pages (like 64K pages with 512M
> > +     huge pages).  */
> > +  if (GL(dl_thp_control) <= 0
> > +      || p_align_max >= thp_pagesize
> > +      || !(thp_mode == thp_mode_always || thp_mode == thp_mode_madvise))
>
> Missing a check for MAX_THP_PAGESIZE?

See the _dl_get_thp_config change above.

> > +    {
> > +      for (i = 0; i < n; i++, c++)
> > + c->mapalign = p_align_max;
> > +      return;
> > +    }
> > +
> > +  /* Set the mapalign field to THP page size only if offset and address
> > +     of the segment are aligned to THP page size, it is read-only and
> > +     its size >= THP page size.  It helps improve THP eligibility and
> > +     reduces TLB pressure.  */
> > +  for (i = 0; i < n; i++, c++)
> > +    if (((c->mapstart | c->mapoff) & (thp_pagesize - 1)) == 0
> > + && (c->mapend - c->mapstart) >= thp_pagesize
> > + && (c->prot & PROT_WRITE) == 0)
> > +      {
> > + c->mapalign = thp_pagesize;
> > + if (thp_mode == thp_mode_madvise)
> > +   c->flags = LOADCMD_POST_MADVISE_THP;
> > +      }
> > +    else
> > +      c->mapalign = p_align_max;
> > +}
> > diff --git a/sysdeps/unix/sysv/linux/dl-map-segment-align.h b/sysdeps/unix/sysv/linux/dl-map-segment-adjust.h
> > similarity index 79%
> > rename from sysdeps/unix/sysv/linux/dl-map-segment-align.h
> > rename to sysdeps/unix/sysv/linux/dl-map-segment-adjust.h
>
> Why rename this hook?

I will keep the name, but change and move it.

>
> > index d9b05181b7..2fbfc66d48 100644
> > --- a/sysdeps/unix/sysv/linux/dl-map-segment-align.h
> > +++ b/sysdeps/unix/sysv/linux/dl-map-segment-adjust.h
> > @@ -1,4 +1,4 @@
> > -/* _dl_map_segment_align.  Linux version.
> > +/* _dl_map_segment_adjust.  Linux version.
> >     Copyright (C) 2026 Free Software Foundation, Inc.
> >     Copyright The GNU Toolchain Authors.
> >     This file is part of the GNU C Library.
> > @@ -19,9 +19,5 @@
> >
> >  #include <dl-load.h>
> >
> > -#ifndef DL_MAP_DEFAULT_THP_PAGESIZE
> > -# define DL_MAP_DEFAULT_THP_PAGESIZE 0
> > -#endif
> > -
> > -extern ElfW (Addr) _dl_map_segment_align
> > -  (const struct loadcmd *, ElfW (Addr)) attribute_hidden;
> > +extern void _dl_map_segment_adjust
> > +  (struct loadcmd *, size_t *, ElfW(Addr)) attribute_hidden;
> > diff --git a/sysdeps/unix/sysv/linux/dl-map-segment-align.c b/sysdeps/unix/sysv/linux/dl-map-segment-align.c
> > deleted file mode 100644
> > index a39e74d91b..0000000000
> > --- a/sysdeps/unix/sysv/linux/dl-map-segment-align.c
> > +++ /dev/null
> > @@ -1,55 +0,0 @@
> > -/* _dl_map_segment_align.  Linux version.
> > -   Copyright (C) 2026 Free Software Foundation, Inc.
> > -   Copyright The GNU Toolchain Authors.
> > -   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/>.  */
> > -
> > -#include <dl-map-segment-align.h>
> > -#include <dl-tunables.h>
> > -#include <hugepages.h>
> > -
> > -ElfW (Addr)
> > -_dl_map_segment_align (const struct loadcmd *c, ElfW (Addr) p_align_max)
> > -{
> > -  static enum thp_mode_t thp_mode = thp_mode_not_supported;
> > -  static unsigned long int thp_pagesize;
> > -
> > -  if (TUNABLE_GET (glibc, elf, thp, int32_t, NULL) == 0)
> > -    return p_align_max;
> > -
> > -  if (__glibc_unlikely (thp_mode == thp_mode_not_supported
> > -                        || thp_pagesize == 0))
> > -    {
> > -      unsigned long int default_thp_pagesize = DL_MAP_DEFAULT_THP_PAGESIZE;
> > -      thp_mode = default_thp_pagesize ? thp_mode_always : __get_thp_mode ();
> > -      thp_pagesize = default_thp_pagesize ? : __get_thp_size ();
> > -    }
> > -
> > -  /* Aligning load segments that are large enough to the PMD size helps
> > -     improve THP eligibility and reduces TLB pressure.
> > -     We cap the huge page size at MAX_THP_PAGESIZE to avoid over-aligning
> > -     on systems with very large normal pages (like 64K pages with 512M
> > -     huge pages). */
> > -  if (thp_mode == thp_mode_always
> > -      && thp_pagesize <= MAX_THP_PAGESIZE
> > -      && ((c->mapstart | c->mapoff) & (thp_pagesize - 1)) == 0
> > -      && (c->mapend - c->mapstart) >= thp_pagesize
> > -      && p_align_max < thp_pagesize
> > -      && (c->prot & PROT_WRITE) == 0)
> > -    return thp_pagesize;
> > -
> > -  return p_align_max;
> > -}
> > diff --git a/sysdeps/unix/sysv/linux/ldsodefs.h b/sysdeps/unix/sysv/linux/ldsodefs.h
> > index c63b649432..e39d9afe34 100644
> > --- a/sysdeps/unix/sysv/linux/ldsodefs.h
> > +++ b/sysdeps/unix/sysv/linux/ldsodefs.h
> > @@ -21,6 +21,9 @@
> >  /* We have the auxiliary vector.  */
> >  #define HAVE_AUX_VECTOR
> >
> > +/* We have transparent huge page.  */
> > +#define HAVE_THP
> > +
> >  /* Get the real definitions.  */
> >  #include_next <ldsodefs.h>
> >
> > diff --git a/sysdeps/unix/sysv/linux/loongarch/Makefile b/sysdeps/unix/sysv/linux/loongarch/Makefile
> > index 0d5f087862..d5beb62440 100644
> > --- a/sysdeps/unix/sysv/linux/loongarch/Makefile
> > +++ b/sysdeps/unix/sysv/linux/loongarch/Makefile
> > @@ -12,3 +12,6 @@ abi-ilp32s-condition := __WORDSIZE == 32 && defined __loongarch_soft_float
> >  abi-ilp32d-condition := __WORDSIZE == 32 && defined __loongarch_double_float
> >  abi-lp64s-condition := __WORDSIZE == 64 && defined __loongarch_soft_float
> >  abi-lp64d-condition := __WORDSIZE == 64 && defined __loongarch_double_float
> > +
> > +# Align THP tests to 32MB.
> > +THP-PAGE-SIZE = 0x2000000
> > diff --git a/sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h b/sysdeps/unix/sysv/linux/loongarch/lp64/dl-exec-post.h
> > similarity index 90%
> > rename from sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h
> > rename to sysdeps/unix/sysv/linux/loongarch/lp64/dl-exec-post.h
> > index c51ee4ac47..fbc58d662c 100644
> > --- a/sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h
> > +++ b/sysdeps/unix/sysv/linux/loongarch/lp64/dl-exec-post.h
> > @@ -1,4 +1,4 @@
> > -/* _dl_map_segment_align.  LoongArch64 Linux version.
> > +/* DL_MAP_DEFAULT_THP_PAGESIZE.  LoongArch64 Linux version.
> >     Copyright (C) 2026 Free Software Foundation, Inc.
> >     Copyright The GNU Toolchain Authors.
> >     This file is part of the GNU C Library.
> > @@ -19,4 +19,4 @@
> >
> >  #define DL_MAP_DEFAULT_THP_PAGESIZE (32 * 1024 * 1024)
> >
> > -#include_next <dl-map-segment-align.h>
> > +#include_next <dl-exec-post.h>
> > diff --git a/sysdeps/unix/sysv/linux/strace-tst-thp.sh b/sysdeps/unix/sysv/linux/strace-tst-thp.sh
> > new file mode 100644
> > index 0000000000..8903717fd4
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/strace-tst-thp.sh
> > @@ -0,0 +1,56 @@
> > +#!/bin/sh
> > +# Run THP test under strace to verify control of the THP segment load.
> > +# 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/>.
> > +
> > +set -e
> > +
> > +rtld="$1"
> > +test_wrapper_env="$2"
> > +run_program_env="$3"
> > +library_path="$4"
> > +test_prog="$5"
> > +
> > +# Test whether strace is available in the test environment.  If not, skip
> > +# the test.
> > +${test_wrapper_env} ${run_program_env} \
> > +  /bin/sh -c "command -v strace" || exit 77
> > +
> > +# Finally the actual test inside the test environment, using the just
> > +# build ld.so and new libraries to run the THP test under strace.
> > +if /bin/sh -c \
> > +  "${test_wrapper_env} ${run_program_env} strace ${rtld} \
> > +   --library-path ${library_path} ${test_prog} 2>&1 \
> > +   | grep -E \"madvise(.*, MADV_HUGEPAGE)\""; then
> > +  case x"${run_program_env}" in
> > +  *glibc.elf.thp=1*)
> > +    exit 0
> > +    ;;
> > +  *)
> > +    exit 1
> > +    ;;
> > +  esac
> > +else
> > +  case x"${run_program_env}" in
> > +  *glibc.elf.thp=0*)
> > +    exit 0
> > +    ;;
> > +  *)
> > +    exit 1
> > +    ;;
> > +  esac
> > +fi
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
> > new file mode 100644
> > index 0000000000..3fd01e9bfe
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
> > @@ -0,0 +1,19 @@
> > +/* Test PDE with THP segment load linked with -Wl,-z,noseparate-code.
> > +   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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-1.c"
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
> > new file mode 100644
> > index 0000000000..d0ae0f1ff0
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
> > @@ -0,0 +1,19 @@
> > +/* Test static with THP segment load linked with -Wl,-z,noseparate-code.
> > +   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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-1.c"
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
> > new file mode 100644
> > index 0000000000..5eb1e005ed
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
> > @@ -0,0 +1,19 @@
> > +/* Test THP segment load linked with -Wl,-z,noseparate-code.
> > +   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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-1.c"
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-pde.c b/sysdeps/unix/sysv/linux/tst-thp-1-pde.c
> > new file mode 100644
> > index 0000000000..d854dd43da
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1-pde.c
> > @@ -0,0 +1,19 @@
> > +/* Test PDE with THP segment load linked with -Wl,-z,separate-code.
> > +   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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-1.c"
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-static.c b/sysdeps/unix/sysv/linux/tst-thp-1-static.c
> > new file mode 100644
> > index 0000000000..66d7e12954
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1-static.c
> > @@ -0,0 +1,19 @@
> > +/* Test static with THP segment load linked with -Wl,-z,separate-code.
> > +   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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-1.c"
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1.c b/sysdeps/unix/sysv/linux/tst-thp-1.c
> > new file mode 100644
> > index 0000000000..49eea7069c
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1.c
> > @@ -0,0 +1,28 @@
> > +/* Test THP segment load linked with -Wl,-z,separate-code.
> > +   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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-align-check.h"
> > +
> > +static int
> > +do_test (void)
> > +{
> > +  check_align ("tst-thp-1");
> > +  return 0;
> > +}
> > +
> > +#include <support/test-driver.c>
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-align-check.h b/sysdeps/unix/sysv/linux/tst-thp-align-check.h
> > new file mode 100644
> > index 0000000000..bcb50c96f8
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-align-check.h
> > @@ -0,0 +1,132 @@
> > +/* Test the THP compatible alignment of PT_LOAD segments.
> > +
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +
> > +   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/>.  */
> > +
> > +#include <fcntl.h>
> > +#include <stdint.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <intprops.h>
> > +#include <inttypes.h>
> > +#include <support/check.h>
> > +#include <support/xstdio.h>
> > +#include <support/xunistd.h>
> > +#define attribute_hidden
> > +#include <hugepages.h> /* For enum thp_mode_t and MAX_THP_PAGESIZE.  */
> > +#undef attribute_hidden
> > +
> > +static unsigned long int
> > +get_thp_size (void)
> > +{
> > +  int fd = open ("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size",
> > +                 O_RDONLY, 0);
> > +  if (fd == -1)
> > +    return 0;
> > +
> > +  char str[INT_BUFSIZE_BOUND (unsigned long int)];
> > +  ssize_t s = read (fd, str, sizeof (str));
> > +  close (fd);
> > +  if (s < 0)
> > +    return 0;
> > +
> > +  unsigned long int r = 0;
> > +  for (ssize_t i = 0; i < s; i++)
> > +    {
> > +      if (str[i] == '\n')
> > +    break;
> > +      r *= 10;
> > +      r += str[i] - '0';
> > +    }
> > +  return r;
> > +}
> > +
> > +static enum thp_mode_t
> > +get_thp_mode (void)
> > +{
> > +  int fd = open ("/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY, 0);
> > +  if (fd == -1)
> > +    return thp_mode_not_supported;
> > +
> > +  static const char mode_always[]  = "[always] madvise never\n";
> > +  static const char mode_madvise[] = "always [madvise] never\n";
> > +  static const char mode_never[]   = "always madvise [never]\n";
> > +
> > +  char str[sizeof(mode_always)];
> > +  ssize_t s = read (fd, str, sizeof (str));
> > +  if (s >= sizeof str || s < 0)
> > +    return thp_mode_not_supported;
> > +  str[s] = '\0';
> > +  close (fd);
> > +
> > +  if (s == sizeof (mode_always) - 1)
> > +    {
> > +      if (strcmp (str, mode_always) == 0)
> > +    return thp_mode_always;
> > +      else if (strcmp (str, mode_madvise) == 0)
> > +    return thp_mode_madvise;
> > +      else if (strcmp (str, mode_never) == 0)
> > +    return thp_mode_never;
> > +    }
> > +  return thp_mode_not_supported;
> > +}
> > +
> > +static void
> > +check_align (const char *name)
> > +{
> > +  unsigned long int thp_size = get_thp_size ();
> > +  enum thp_mode_t thp_mode = get_thp_mode ();
> > +
> > +  if (thp_size == 0)
> > +    {
> > +      FAIL_UNSUPPORTED ("unable to get THP size.\n");
> > +      return;
> > +    }
> > +
> > +  if (thp_size > MAX_THP_PAGESIZE)
> > +    {
> > +      FAIL_UNSUPPORTED ("THP size exceeds MAX_THP_PAGESIZE.\n");
> > +      return;
> > +    }
> > +
> > +  if (thp_mode != thp_mode_always && thp_mode != thp_mode_madvise)
> > +    {
> > +      FAIL_UNSUPPORTED ("THP mode is not always nor madvise.\n");
> > +      return;
> > +    }
> > +
> > +  FILE *f = xfopen ("/proc/self/maps", "r");
> > +  char *line = NULL;
> > +  size_t len;
> > +
> > +  while (xgetline (&line, &len, f))
> > +    {
> > +      uintptr_t from, to;
> > +      char *prot = NULL, *path = NULL;
> > +      int r = sscanf (line, "%" SCNxPTR "-%" SCNxPTR "%ms%*s%*s%*s%ms",
> > +                      &from, &to, &prot, &path);
> > +
> > +      TEST_VERIFY (r == 3 || r == 4);
> > +
> > +      if (strstr (prot, "x") && strstr (path, name))
> > +        TEST_COMPARE (from % thp_size, 0);
> > +
> > +      free (path);
> > +    }
> > +
> > +  free (line);
> > +  xfclose (f);
> > +}
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-align.c b/sysdeps/unix/sysv/linux/tst-thp-align.c
> > new file mode 100644
> > index 0000000000..2e44109ba6
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-align.c
> > @@ -0,0 +1,36 @@
> > +/* Test the THP compatible alignment of PT_LOAD segments.
> > +
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +
> > +   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/>.  */
> > +
> > +#include <support/xdlfcn.h>
> > +#include "tst-thp-align-check.h"
> > +
> > +#define THP_SIZE_MOD_NAME "tst-thp-size-mod.so"
> > +
> > +static int
> > +do_test (void)
> > +{
> > +  void *dl;
> > +
> > +  dl = xdlopen (THP_SIZE_MOD_NAME, RTLD_NOW);
> > +  check_align (THP_SIZE_MOD_NAME);
> > +  xdlclose (dl);
> > +
> > +  return 0;
> > +}
> > +
> > +#include <support/test-driver.c>
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-size-mod.S b/sysdeps/unix/sysv/linux/tst-thp-size-mod.S
> > new file mode 100644
> > index 0000000000..add66dfb52
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-size-mod.S
> > @@ -0,0 +1,24 @@
> > +/* A module that provides THP code size for testing.
> > +
> > +   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/>.  */
> > +
> > +/* Create a 32MB text section to enable transparent huge page load.  */
> > + .text
> > + .globl thp_size
> > +thp_size:
> > + .space 32 * 1024 * 1024
> > diff --git a/sysdeps/unix/sysv/linux/x86/dl-exec-post.h b/sysdeps/unix/sysv/linux/x86/dl-exec-post.h
> > new file mode 100644
> > index 0000000000..0fa0b5e0e5
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/x86/dl-exec-post.h
> > @@ -0,0 +1,22 @@
> > +/* DL_MAP_DEFAULT_THP_PAGESIZE.  Linux/x86 version.
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +   Copyright The GNU Toolchain Authors.
> > +   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/>.  */
> > +
> > +#define DL_MAP_DEFAULT_THP_PAGESIZE (2 * 1024 * 1024)
> > +
> > +#include_next <dl-exec-post.h>
> > --
> > 2.53.0
> >
>
> Thanks,
> Rui
>

Thanks.

-- 
H.J.


More information about the Libc-alpha mailing list