I wrote:
> Your thought of maybe applying some compiler ju-jitsu led me to a
> different research direction: if we can get the compiler to lay out
> the catalog structs as if DOUBLEALIGN and LONGALIGN were 4, then
> problem solved without need for any user-table compatibility break.
> And we can: it looks like inserting
> #pragma pack(4)
> before each catalog struct definition (and then "#pragma pack()"
> afterwards to restore the normal rules) would do the trick.
Here's a POC patch to fix AIX's alignment problems that way.
I feel much better about this approach than the other one ...
regards, tom lane
From fe5383dd30b93ba738ea1300f3bdf64a016a77ec Mon Sep 17 00:00:00 2001
From: Tom Lane <[email protected]>
Date: Fri, 6 Feb 2026 21:37:27 -0500
Subject: [PATCH v4] Cope with AIX's alignment woes by using _Pragma("pack").
Because we assume that int64 and double have the same alignment
requirement, AIX's default behavior that alignof(double) = 4 while
alignof(int64) = 8 is a headache. This POC patch shows how we might
solve that in a maintainable way without breaking pg_upgrade-ability
of existing AIX databases.
The behavior of v16 and earlier on AIX is that they'd align all
typalign = 'd' types on 4-byte boundaries, because that's what
ALIGNOF_DOUBLE comes out to. That's not ideal, because it probably
incurs some inefficiency when fetching a value that's not 8-aligned,
but that doesn't seem like a sufficient reason to break
pg_upgrade-ability. Hence we want to preserve that behavior.
The problem is that we need to overlay C structs onto catalog tuples,
and int8 fields in those struct declarations may not be aligned
correctly.
In the old branches we had some annoying rules about ordering catalog
columns to avoid alignment problems, but nobody wants to resurrect
those. However, there's a better answer: make the compiler construe
those struct declarations the way we need it to by using the pack(N)
pragma. This requires no manual effort to maintain going forward:
we only have to insert the pragma into all the catalog *.h files.
We did not have this option when the AIX port was first made. This
patch depends on the C99 feature _Pragma(), as well as the pack(N)
pragma which is probably gcc-specific. But now that we've agreed
to toss xlc support out the window, there doesn't seem to be a reason
not to go this way.
In this POC patch, I only bothered to add the BEGIN/END_CATALOG_STRUCT
macros to pg_subscription.h, but of course we'd want to add them to
all the catalog headers if we do this for real. Also, since we don't
actually have any problematic catalog columns as of HEAD, I moved
pg_subscription's subskiplsn column to artificially create an
alignment hazard. We'll probably leave subskiplsn where it is in
a final patch, since there's a nonzero hazard of breaking user
queries if we re-order the catalog's columns.
In passing, I got rid of LONGALIGN[_DOWN] along with the configure
probes for ALIGNOF_LONG. We were not using those anywhere and it
seems highly unlikely that we'd do so in future. Instead supply
INT64ALIGN[_DOWN], which isn't used either but at least could
have a good reason to be used.
Discussion: https://postgr.es/m/[email protected]
---
configure | 64 ++++-----------------------
configure.ac | 30 ++++---------
meson.build | 31 ++++++-------
src/include/c.h | 4 +-
src/include/catalog/genbki.h | 19 ++++++++
src/include/catalog/pg_subscription.h | 10 +++--
src/include/pg_config.h.in | 3 --
7 files changed, 59 insertions(+), 102 deletions(-)
diff --git a/configure b/configure
index ba293931878..6ff1c9a51ed 100755
--- a/configure
+++ b/configure
@@ -17137,41 +17137,6 @@ cat >>confdefs.h <<_ACEOF
_ACEOF
-# The cast to long int works around a bug in the HP C Compiler,
-# see AC_CHECK_SIZEOF for more information.
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking alignment of long" >&5
-$as_echo_n "checking alignment of long... " >&6; }
-if ${ac_cv_alignof_long+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- if ac_fn_c_compute_int "$LINENO" "(long int) offsetof (ac__type_alignof_, y)" "ac_cv_alignof_long" "$ac_includes_default
-#ifndef offsetof
-# define offsetof(type, member) ((char *) &((type *) 0)->member - (char *) 0)
-#endif
-typedef struct { char x; long y; } ac__type_alignof_;"; then :
-
-else
- if test "$ac_cv_type_long" = yes; then
- { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error 77 "cannot compute alignment of long
-See \`config.log' for more details" "$LINENO" 5; }
- else
- ac_cv_alignof_long=0
- fi
-fi
-
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_alignof_long" >&5
-$as_echo "$ac_cv_alignof_long" >&6; }
-
-
-
-cat >>confdefs.h <<_ACEOF
-#define ALIGNOF_LONG $ac_cv_alignof_long
-_ACEOF
-
-
# The cast to long int works around a bug in the HP C Compiler,
# see AC_CHECK_SIZEOF for more information.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking alignment of int64_t" >&5
@@ -17245,27 +17210,16 @@ _ACEOF
# Compute maximum alignment of any basic type.
#
-# We require 'double' to have the strictest alignment among the basic types,
-# because otherwise the C ABI might impose 8-byte alignment on some of the
-# other C types that correspond to TYPALIGN_DOUBLE SQL types. That could
-# cause a mismatch between the tuple layout and the C struct layout of a
-# catalog tuple. We used to carefully order catalog columns such that any
-# fixed-width, attalign=4 columns were at offsets divisible by 8 regardless
-# of MAXIMUM_ALIGNOF to avoid that, but we no longer support any platforms
-# where TYPALIGN_DOUBLE != MAXIMUM_ALIGNOF.
-#
-# We assume without checking that long's alignment is at least as strong as
-# char, short, or int. Note that we intentionally do not consider any types
-# wider than 64 bits, as allowing MAXIMUM_ALIGNOF to exceed 8 would be too
-# much of a penalty for disk and memory space.
+# We assume without checking that the maximum alignment requirement is that
+# of int64_t and/or double. (On most platforms those are the same, but not
+# everywhere.) Note that we intentionally do not consider any types wider
+# than 64 bits, as allowing MAXIMUM_ALIGNOF to exceed 8 would be too much
+# of a penalty for disk and memory space.
-MAX_ALIGNOF=$ac_cv_alignof_double
-
-if test $ac_cv_alignof_long -gt $MAX_ALIGNOF ; then
- as_fn_error $? "alignment of 'long' is greater than the alignment of 'double'" "$LINENO" 5
-fi
-if test $ac_cv_alignof_int64_t -gt $MAX_ALIGNOF ; then
- as_fn_error $? "alignment of 'int64_t' is greater than the alignment of 'double'" "$LINENO" 5
+if test $ac_cv_alignof_int64_t -gt $ac_cv_alignof_double ; then
+ MAX_ALIGNOF=$ac_cv_alignof_int64_t
+else
+ MAX_ALIGNOF=$ac_cv_alignof_double
fi
cat >>confdefs.h <<_ACEOF
diff --git a/configure.ac b/configure.ac
index 412fe358a2f..0cd6299d489 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2032,33 +2032,21 @@ AC_CHECK_SIZEOF([intmax_t])
AC_CHECK_ALIGNOF(short)
AC_CHECK_ALIGNOF(int)
-AC_CHECK_ALIGNOF(long)
AC_CHECK_ALIGNOF(int64_t)
AC_CHECK_ALIGNOF(double)
# Compute maximum alignment of any basic type.
#
-# We require 'double' to have the strictest alignment among the basic types,
-# because otherwise the C ABI might impose 8-byte alignment on some of the
-# other C types that correspond to TYPALIGN_DOUBLE SQL types. That could
-# cause a mismatch between the tuple layout and the C struct layout of a
-# catalog tuple. We used to carefully order catalog columns such that any
-# fixed-width, attalign=4 columns were at offsets divisible by 8 regardless
-# of MAXIMUM_ALIGNOF to avoid that, but we no longer support any platforms
-# where TYPALIGN_DOUBLE != MAXIMUM_ALIGNOF.
-#
-# We assume without checking that long's alignment is at least as strong as
-# char, short, or int. Note that we intentionally do not consider any types
-# wider than 64 bits, as allowing MAXIMUM_ALIGNOF to exceed 8 would be too
-# much of a penalty for disk and memory space.
-
-MAX_ALIGNOF=$ac_cv_alignof_double
+# We assume without checking that the maximum alignment requirement is that
+# of int64_t and/or double. (On most platforms those are the same, but not
+# everywhere.) Note that we intentionally do not consider any types wider
+# than 64 bits, as allowing MAXIMUM_ALIGNOF to exceed 8 would be too much
+# of a penalty for disk and memory space.
-if test $ac_cv_alignof_long -gt $MAX_ALIGNOF ; then
- AC_MSG_ERROR([alignment of 'long' is greater than the alignment of 'double'])
-fi
-if test $ac_cv_alignof_int64_t -gt $MAX_ALIGNOF ; then
- AC_MSG_ERROR([alignment of 'int64_t' is greater than the alignment of 'double'])
+if test $ac_cv_alignof_int64_t -gt $ac_cv_alignof_double ; then
+ MAX_ALIGNOF=$ac_cv_alignof_int64_t
+else
+ MAX_ALIGNOF=$ac_cv_alignof_double
fi
AC_DEFINE_UNQUOTED(MAXIMUM_ALIGNOF, $MAX_ALIGNOF, [Define as the maximum alignment requirement of any C data type.])
diff --git a/meson.build b/meson.build
index 0722b16927e..bbcfbdc1c65 100644
--- a/meson.build
+++ b/meson.build
@@ -1798,32 +1798,27 @@ endif
# Determine memory alignment requirements for the basic C data types.
-alignof_types = ['short', 'int', 'long', 'double']
+alignof_types = ['short', 'int', 'int64_t', 'double']
foreach t : alignof_types
- align = cc.alignment(t, args: test_c_args)
+ align = cc.alignment(t, args: test_c_args, prefix: '#include <stdint.h>')
cdata.set('ALIGNOF_@0@'.format(t.to_upper()), align)
endforeach
# Compute maximum alignment of any basic type.
#
-# We require 'double' to have the strictest alignment among the basic types,
-# because otherwise the C ABI might impose 8-byte alignment on some of the
-# other C types that correspond to TYPALIGN_DOUBLE SQL types. That could
-# cause a mismatch between the tuple layout and the C struct layout of a
-# catalog tuple. We used to carefully order catalog columns such that any
-# fixed-width, attalign=4 columns were at offsets divisible by 8 regardless
-# of MAXIMUM_ALIGNOF to avoid that, but we no longer support any platforms
-# where TYPALIGN_DOUBLE != MAXIMUM_ALIGNOF.
-#
-# We assume without checking that int64_t's alignment is at least as strong
-# as long, char, short, or int. Note that we intentionally do not consider
-# any types wider than 64 bits, as allowing MAXIMUM_ALIGNOF to exceed 8
-# would be too much of a penalty for disk and memory space.
+# We assume without checking that the maximum alignment requirement is that
+# of int64_t and/or double. (On most platforms those are the same, but not
+# everywhere.) Note that we intentionally do not consider any types wider
+# than 64 bits, as allowing MAXIMUM_ALIGNOF to exceed 8 would be too much
+# of a penalty for disk and memory space.
+
+alignof_int64_t = cdata.get('ALIGNOF_INT64_T')
alignof_double = cdata.get('ALIGNOF_DOUBLE')
-if cc.alignment('int64_t', args: test_c_args, prefix: '#include <stdint.h>') > alignof_double
- error('alignment of int64_t is greater than the alignment of double')
+if alignof_int64_t > alignof_double
+ cdata.set('MAXIMUM_ALIGNOF', alignof_int64_t)
+else
+ cdata.set('MAXIMUM_ALIGNOF', alignof_double)
endif
-cdata.set('MAXIMUM_ALIGNOF', alignof_double)
cdata.set('SIZEOF_LONG', cc.sizeof('long', args: test_c_args))
cdata.set('SIZEOF_LONG_LONG', cc.sizeof('long long', args: test_c_args))
diff --git a/src/include/c.h b/src/include/c.h
index 063eac9808c..e553c57b3ca 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -821,7 +821,7 @@ typedef NameData *Name;
#define SHORTALIGN(LEN) TYPEALIGN(ALIGNOF_SHORT, (LEN))
#define INTALIGN(LEN) TYPEALIGN(ALIGNOF_INT, (LEN))
-#define LONGALIGN(LEN) TYPEALIGN(ALIGNOF_LONG, (LEN))
+#define INT64ALIGN(LEN) TYPEALIGN(ALIGNOF_INT64_T, (LEN))
#define DOUBLEALIGN(LEN) TYPEALIGN(ALIGNOF_DOUBLE, (LEN))
#define MAXALIGN(LEN) TYPEALIGN(MAXIMUM_ALIGNOF, (LEN))
/* MAXALIGN covers only built-in types, not buffers */
@@ -833,7 +833,7 @@ typedef NameData *Name;
#define SHORTALIGN_DOWN(LEN) TYPEALIGN_DOWN(ALIGNOF_SHORT, (LEN))
#define INTALIGN_DOWN(LEN) TYPEALIGN_DOWN(ALIGNOF_INT, (LEN))
-#define LONGALIGN_DOWN(LEN) TYPEALIGN_DOWN(ALIGNOF_LONG, (LEN))
+#define INT64ALIGN_DOWN(LEN) TYPEALIGN_DOWN(ALIGNOF_INT64_T, (LEN))
#define DOUBLEALIGN_DOWN(LEN) TYPEALIGN_DOWN(ALIGNOF_DOUBLE, (LEN))
#define MAXALIGN_DOWN(LEN) TYPEALIGN_DOWN(MAXIMUM_ALIGNOF, (LEN))
#define BUFFERALIGN_DOWN(LEN) TYPEALIGN_DOWN(ALIGNOF_BUFFER, (LEN))
diff --git a/src/include/catalog/genbki.h b/src/include/catalog/genbki.h
index 4606a29160e..0a5299c8e01 100644
--- a/src/include/catalog/genbki.h
+++ b/src/include/catalog/genbki.h
@@ -19,6 +19,25 @@
#ifndef GENBKI_H
#define GENBKI_H
+/*
+ * These macros should be written before and after each catalog structure
+ * definition. On most platforms they do nothing, but on some platforms
+ * we need special hacks to coax the compiler into laying out the catalog
+ * struct compatibly with our tuple forming/deforming rules.
+ *
+ * On AIX, where ALIGNOF_DOUBLE < ALIGNOF_INT64_T, we need to coerce int64
+ * catalog fields to be aligned on only 4-byte boundaries. Ideally we'd
+ * write this like pack(ALIGNOF_DOUBLE), but gcc seems unwilling to take
+ * anything but a plain string literal as the argument of _Pragma.
+ */
+#if ALIGNOF_DOUBLE < ALIGNOF_INT64_T
+#define BEGIN_CATALOG_STRUCT _Pragma("pack(4)")
+#define END_CATALOG_STRUCT _Pragma("pack()")
+#else
+#define BEGIN_CATALOG_STRUCT
+#define END_CATALOG_STRUCT
+#endif
+
/* Introduces a catalog's structure definition */
#define CATALOG(name,oid,oidmacro) typedef struct CppConcat(FormData_,name)
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index f3571d2bfcf..577c240f29c 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -40,6 +40,8 @@
* here, be sure to update that (or, if the new column is not to be publicly
* readable, update associated comments and catalogs.sgml instead).
*/
+BEGIN_CATALOG_STRUCT
+
CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(6101,SubscriptionRelation_Rowtype_Id) BKI_SCHEMA_MACRO
{
Oid oid; /* oid */
@@ -47,9 +49,6 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
Oid subdbid BKI_LOOKUP(pg_database); /* Database the
* subscription is in. */
- XLogRecPtr subskiplsn; /* All changes finished at this LSN are
- * skipped */
-
NameData subname; /* Name of the subscription */
Oid subowner BKI_LOOKUP(pg_authid); /* Owner of the subscription */
@@ -78,6 +77,9 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
* slots) in the upstream database are enabled
* to be synchronized to the standbys. */
+ XLogRecPtr subskiplsn; /* All changes finished at this LSN are
+ * skipped */
+
bool subretaindeadtuples; /* True if dead tuples useful for
* conflict detection are retained */
@@ -108,6 +110,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
#endif
} FormData_pg_subscription;
+END_CATALOG_STRUCT
+
typedef FormData_pg_subscription *Form_pg_subscription;
DECLARE_TOAST_WITH_MACRO(pg_subscription, 4183, 4184, PgSubscriptionToastTable, PgSubscriptionToastIndex);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index c089f2252c3..7410192b806 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -12,9 +12,6 @@
/* The normal alignment of `int64_t', in bytes. */
#undef ALIGNOF_INT64_T
-/* The normal alignment of `long', in bytes. */
-#undef ALIGNOF_LONG
-
/* The normal alignment of `PG_INT128_TYPE', in bytes. */
#undef ALIGNOF_PG_INT128_TYPE
--
2.43.7