There are a number of places where palloc()/malloc()/etc. was used solely to obtain an aligned buffer. We can do these much simpler by using alignas with a local variable instead. See attached patch.

One of these revealed a small problem with how pgindent handles alignas (it doesn't know about it and it might or might not work well depending on context), so I added a workaround into pgindent to fix that. (Or we could try to reshuffle that code to avoid the problem.)
From 9e5c5d6a660791aaab06bab10272260da109dfb6 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Tue, 8 Sep 2026 11:42:24 +0200
Subject: [PATCH 1/2] Use C11 alignas instead of palloc/malloc for alignment

Replace several cases where palloc()/malloc()/etc. was used solely to
obtain an aligned buffer.  Use alignas with a local variable instead.

The previous alignment guarantees are carried over.  palloc-based
allocations are replaced by alignas(MAXIMUM_ALIGNOF).  Theoretically,
malloc-based allocations should be replaced by alignas(max_align_t),
but MSVC doesn't provide max_align_t, and so we use MAXIMUM_ALIGNOF
here as well.  They should be the same in practice.

The allocations in InitWalRecovery() are not converted, because the
comment says it is also this way to avoid wasting storage.  The
comment in XLogReaderAllocate(), on the other hand, was probably
copied from InitWalRecovery(), but the part of the comment about
wasting storage does not make sense in that context, so it is
converted.

FIXME: indent in xlogreader.h
---
 src/backend/access/transam/xloginsert.c   | 28 ++++++++---------------
 src/backend/access/transam/xlogreader.c   | 17 --------------
 src/backend/access/transam/xlogrecovery.c |  3 ++-
 src/backend/commands/sequence_xlog.c      |  9 +++-----
 src/backend/storage/file/copydir.c        | 12 ++--------
 src/backend/storage/ipc/dsm_impl.c        | 10 ++++----
 src/backend/storage/smgr/md.c             |  4 +---
 src/bin/pg_resetwal/pg_resetwal.c         |  7 ++----
 src/include/access/xlogreader.h           |  5 ++--
 9 files changed, 26 insertions(+), 69 deletions(-)

diff --git a/src/backend/access/transam/xloginsert.c 
b/src/backend/access/transam/xloginsert.c
index c9aff944a2e..70cbe9d709b 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -105,17 +105,6 @@ static uint64 mainrdata_len;       /* total # of bytes in 
chain */
 /* flags for the in-progress insertion */
 static uint8 curinsert_flags = 0;
 
-/*
- * These are used to hold the record header while constructing a record.
- * 'hdr_scratch' is not a plain variable, but is palloc'd at initialization,
- * because we want it to be MAXALIGNed and padding bytes zeroed.
- *
- * For simplicity, it's allocated large enough to hold the headers for any
- * WAL record.
- */
-static XLogRecData hdr_rdt;
-static char *hdr_scratch = NULL;
-
 #define SizeOfXlogOrigin       (sizeof(ReplOriginId) + sizeof(char))
 #define SizeOfXLogTransactionId        (sizeof(TransactionId) + sizeof(char))
 
@@ -622,6 +611,16 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
                                   XLogRecPtr *fpw_lsn, int *num_fpi, uint64 
*fpi_bytes,
                                   bool *topxid_included)
 {
+       /*
+        * These are used to hold the record header while constructing a record.
+        * 'hdr_scratch' must be MAXALIGNed and padding bytes zeroed.
+        *
+        * For simplicity, it's allocated large enough to hold the headers for 
any
+        * WAL record.
+        */
+       static XLogRecData hdr_rdt;
+       static alignas(MAXIMUM_ALIGNOF) char hdr_scratch[HEADER_SCRATCH_SIZE];
+
        XLogRecData *rdt;
        uint64          total_len = 0;
        int                     block_id;
@@ -1430,11 +1429,4 @@ InitXLogInsert(void)
                                                                        
sizeof(XLogRecData) * XLR_NORMAL_RDATAS);
                max_rdatas = XLR_NORMAL_RDATAS;
        }
-
-       /*
-        * Allocate a buffer to hold the header information for a WAL record.
-        */
-       if (hdr_scratch == NULL)
-               hdr_scratch = MemoryContextAllocZero(xloginsert_cxt,
-                                                                               
         HEADER_SCRATCH_SIZE);
 }
diff --git a/src/backend/access/transam/xlogreader.c 
b/src/backend/access/transam/xlogreader.c
index 7db7c273b0c..3cd86dedc25 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -119,21 +119,6 @@ XLogReaderAllocate(int wal_segment_size, const char 
*waldir,
        /* initialize caller-provided support functions */
        state->routine = *routine;
 
-       /*
-        * Permanently allocate readBuf.  We do it this way, rather than just
-        * making a static array, for two reasons: (1) no need to waste the
-        * storage in most instantiations of the backend; (2) a static char 
array
-        * isn't guaranteed to have any particular alignment, whereas
-        * palloc_extended() will provide MAXALIGN'd storage.
-        */
-       state->readBuf = (char *) palloc_extended(XLOG_BLCKSZ,
-                                                                               
          MCXT_ALLOC_NO_OOM);
-       if (!state->readBuf)
-       {
-               pfree(state);
-               return NULL;
-       }
-
        /* Initialize segment info. */
        WALOpenSegmentInit(&state->seg, &state->segcxt, wal_segment_size,
                                           waldir);
@@ -145,7 +130,6 @@ XLogReaderAllocate(int wal_segment_size, const char *waldir,
                                                                                
  MCXT_ALLOC_NO_OOM);
        if (!state->errormsg_buf)
        {
-               pfree(state->readBuf);
                pfree(state);
                return NULL;
        }
@@ -171,7 +155,6 @@ XLogReaderFree(XLogReaderState *state)
        pfree(state->errormsg_buf);
        if (state->readRecordBuf)
                pfree(state->readRecordBuf);
-       pfree(state->readBuf);
        pfree(state);
 }
 
diff --git a/src/backend/access/transam/xlogrecovery.c 
b/src/backend/access/transam/xlogrecovery.c
index acac97e89d3..c7b3aa77eff 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -535,7 +535,8 @@ InitWalRecovery(ControlFileData *ControlFile, bool 
*wasShutdown_ptr,
         * it this way, rather than just making static arrays, for two reasons:
         * (1) no need to waste the storage in most instantiations of the 
backend;
         * (2) a static char array isn't guaranteed to have any particular
-        * alignment, whereas palloc() will provide MAXALIGN'd storage.
+        * alignment, whereas palloc() will provide MAXALIGN'd storage. 
(Although
+        * the latter could be handled with alignas.)
         */
        replay_image_masked = (char *) palloc(BLCKSZ);
        primary_image_masked = (char *) palloc(BLCKSZ);
diff --git a/src/backend/commands/sequence_xlog.c 
b/src/backend/commands/sequence_xlog.c
index fcb3230cf3b..212e321d806 100644
--- a/src/backend/commands/sequence_xlog.c
+++ b/src/backend/commands/sequence_xlog.c
@@ -26,7 +26,8 @@ seq_redo(XLogReaderState *record)
        uint8           info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
        Buffer          buffer;
        Page            page;
-       Page            localpage;
+       alignas(MAXIMUM_ALIGNOF) char localpage_buf[BLCKSZ];
+       Page            localpage = (Page) localpage_buf;
        char       *item;
        Size            itemsz;
        xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
@@ -44,10 +45,8 @@ seq_redo(XLogReaderState *record)
         * is examining the page concurrently; so we mustn't transiently trash 
the
         * buffer.  The solution is to build the correct new page contents in
         * local workspace and then memcpy into the buffer.  Then only bytes 
that
-        * are supposed to change will change, even transiently. We must palloc
-        * the local page for alignment reasons.
+        * are supposed to change will change, even transiently.
         */
-       localpage = (Page) palloc(BufferGetPageSize(buffer));
 
        PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
        sm = (sequence_magic *) PageGetSpecialPointer(localpage);
@@ -65,8 +64,6 @@ seq_redo(XLogReaderState *record)
        MarkBufferDirty(buffer);
        XLogFlushBufferForRedoIfInit(record, 0, buffer);
        UnlockReleaseBuffer(buffer);
-
-       pfree(localpage);
 }
 
 /*
diff --git a/src/backend/storage/file/copydir.c 
b/src/backend/storage/file/copydir.c
index ee42c796f77..3c78798e480 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -133,16 +133,13 @@ copydir(const char *fromdir, const char *todir, bool 
recurse)
 void
 copy_file(const char *fromfile, const char *tofile)
 {
-       char       *buffer;
+       alignas(MAXIMUM_ALIGNOF) char buffer[8 * BLCKSZ];
        int                     srcfd;
        int                     dstfd;
        ssize_t         nbytes;
        off_t           offset;
        off_t           flush_offset;
 
-       /* Size of copy buffer (read and write requests) */
-#define COPY_BUF_SIZE (8 * BLCKSZ)
-
        /*
         * Size of data flush requests.  It seems beneficial on most platforms 
to
         * do this every 1MB or so.  But macOS, at least with early releases of
@@ -155,9 +152,6 @@ copy_file(const char *fromfile, const char *tofile)
 #define FLUSH_DISTANCE (1024 * 1024)
 #endif
 
-       /* Use palloc to ensure we get a maxaligned buffer */
-       buffer = palloc(COPY_BUF_SIZE);
-
        /*
         * Open the files
         */
@@ -194,7 +188,7 @@ copy_file(const char *fromfile, const char *tofile)
                }
 
                pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_READ);
-               nbytes = read(srcfd, buffer, COPY_BUF_SIZE);
+               nbytes = read(srcfd, buffer, sizeof buffer);
                pgstat_report_wait_end();
                if (nbytes < 0)
                        ereport(ERROR,
@@ -228,8 +222,6 @@ copy_file(const char *fromfile, const char *tofile)
                ereport(ERROR,
                                (errcode_for_file_access(),
                                 errmsg("could not close file \"%s\": %m", 
fromfile)));
-
-       pfree(buffer);
 }
 
 /*
diff --git a/src/backend/storage/ipc/dsm_impl.c 
b/src/backend/storage/ipc/dsm_impl.c
index e8c07805f59..23869c1e7a3 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -867,13 +867,11 @@ dsm_impl_mmap(dsm_op op, dsm_handle handle, Size 
request_size,
        else
        {
                /*
-                * Allocate a buffer full of zeros.
-                *
-                * Note: palloc zbuffer, instead of just using a local char 
array, to
-                * ensure it is reasonably well-aligned; this may save a few 
cycles
-                * transferring data to the kernel.
+                * A buffer full of zeros.  alignas ensures it is reasonably
+                * well-aligned, which may save a few cycles transferring it to 
the
+                * kernel.
                 */
-               char       *zbuffer = (char *) palloc0(ZBUFFER_SIZE);
+               alignas(MAXIMUM_ALIGNOF) const char zbuffer[ZBUFFER_SIZE] = {0};
                Size            remaining = request_size;
                bool            success = true;
 
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 780c88c0630..784be3f96eb 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -1823,13 +1823,11 @@ _mdfd_getseg(SMgrRelation reln, ForkNumber forknum, 
BlockNumber blkno,
                         */
                        if (nblocks < ((BlockNumber) RELSEG_SIZE))
                        {
-                               char       *zerobuf = palloc_aligned(BLCKSZ, 
PG_IO_ALIGN_SIZE,
-                                                                               
                         MCXT_ALLOC_ZERO);
+                               alignas(PG_IO_ALIGN_SIZE) const char 
zerobuf[BLCKSZ] = {0};
 
                                mdextend(reln, forknum,
                                                 nextsegno * ((BlockNumber) 
RELSEG_SIZE) - 1,
                                                 zerobuf, skipFsync);
-                               pfree(zerobuf);
                        }
                        flags = O_CREAT;
                }
diff --git a/src/bin/pg_resetwal/pg_resetwal.c 
b/src/bin/pg_resetwal/pg_resetwal.c
index 79f3085d769..015f6449169 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -601,7 +601,7 @@ read_controlfile(void)
 {
        int                     fd;
        ssize_t         len;
-       char       *buffer;
+       alignas(MAXIMUM_ALIGNOF) char buffer[PG_CONTROL_FILE_SIZE];
        pg_crc32c       crc;
 
        if ((fd = open(XLOG_CONTROL_FILE, O_RDONLY | PG_BINARY, 0)) < 0)
@@ -621,10 +621,7 @@ read_controlfile(void)
                exit(1);
        }
 
-       /* Use malloc to ensure we have a maxaligned buffer */
-       buffer = (char *) pg_malloc(PG_CONTROL_FILE_SIZE);
-
-       len = read(fd, buffer, PG_CONTROL_FILE_SIZE);
+       len = read(fd, buffer, sizeof buffer);
        if (len < 0)
                pg_fatal("could not read file \"%s\": %m", XLOG_CONTROL_FILE);
        close(fd);
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 4a9a687e879..a2563458018 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -260,10 +260,9 @@ struct XLogReaderState
        DecodedXLogRecord *decode_queue_tail;   /* newest decoded record */
 
        /*
-        * Buffer for currently read page (XLOG_BLCKSZ bytes, valid up to at 
least
-        * readLen bytes)
+        * Buffer for currently read page (valid up to at least readLen bytes)
         */
-       char       *readBuf;
+                               alignas(MAXIMUM_ALIGNOF) char 
readBuf[XLOG_BLCKSZ];
        uint32          readLen;
 
        /* last read XLOG position for data currently in readBuf */
-- 
2.55.0

From 9462239c90db4abb61b2dc7f8b48ffbf8de6d8da Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Tue, 8 Sep 2026 11:42:24 +0200
Subject: [PATCH 2/2] pgindent: Fix indentation of alignas() in struct members

pg_bsd_indent doesn't know about alignas().  A struct member using
alignas() ends up being treated as the variable name of the preceding
member's declaration and gets indented to the declaration column.
This only shows up for members that are not the first member of the
struct.

Have pgindent temporarily disguise alignas(...) as a plain identifier
to avoid that.

Also reindent the one affected place in xlogreader.h.
---
 src/include/access/xlogreader.h |  2 +-
 src/tools/pgindent/pgindent     | 10 ++++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index a2563458018..6e27b30fb35 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -262,7 +262,7 @@ struct XLogReaderState
        /*
         * Buffer for currently read page (valid up to at least readLen bytes)
         */
-                               alignas(MAXIMUM_ALIGNOF) char 
readBuf[XLOG_BLCKSZ];
+       alignas(MAXIMUM_ALIGNOF) char readBuf[XLOG_BLCKSZ];
        uint32          readLen;
 
        /* last read XLOG position for data currently in readBuf */
diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent
index 004b8fcab00..b8e42515104 100755
--- a/src/tools/pgindent/pgindent
+++ b/src/tools/pgindent/pgindent
@@ -263,6 +263,13 @@ sub pre_indent
        # Protect wrapping in CATALOG()
        $source =~ s!^(CATALOG\(.*)$!/*$1*/!gm;
 
+       # pg_bsd_indent doesn't know about alignas().  A struct member
+       # using alignas() ends up being treated as the variable name of
+       # the preceding member's declaration and gets indented to the
+       # declaration column.  Disguise alignas(...) as a plain identifier
+       # to avoid that.
+       $source =~ s!\balignas\(([^()\n]*)\)!alignas_${1}_!g;
+
        return $source;
 }
 
@@ -270,6 +277,9 @@ sub post_indent
 {
        my $source = shift;
 
+       # Restore alignas(...)
+       $source =~ s!\balignas_(.*?)_(?=\s)!alignas($1)!g;
+
        # Restore CATALOG lines
        $source =~ s!^/\*(CATALOG\(.*)\*/$!$1!gm;
 
-- 
2.55.0

Reply via email to