Prevent int128 from requiring more than MAXALIGN alignment.
authorTom Lane
Tue, 14 Nov 2017 22:49:49 +0000 (17:49 -0500)
committerTom Lane
Tue, 14 Nov 2017 22:49:49 +0000 (17:49 -0500)
Our initial work with int128 neglected alignment considerations, an
oversight that came back to bite us in bug #14897 from Vincent Lachenal.
It is unsurprising that int128 might have a 16-byte alignment requirement;
what's slightly more surprising is that even notoriously lax Intel chips
sometimes enforce that.

Raising MAXALIGN seems out of the question: the costs in wasted disk and
memory space would be significant, and there would also be an on-disk
compatibility break.  Nor does it seem very practical to try to allow some
data structures to have more-than-MAXALIGN alignment requirement, as we'd
have to push knowledge of that throughout various code that copies data
structures around.

The only way out of the box is to make type int128 conform to the system's
alignment assumptions.  Fortunately, gcc supports that via its
__attribute__(aligned()) pragma; and since we don't currently support
int128 on non-gcc-workalike compilers, we shouldn't be losing any platform
support this way.

Although we could have just done pg_attribute_aligned(MAXIMUM_ALIGNOF) and
called it a day, I did a little bit of extra work to make the code more
portable than that: it will also support int128 on compilers without
__attribute__(aligned()), if the native alignment of their 128-bit-int
type is no more than that of int64.

Add a regression test case that exercises the one known instance of the
problem, in parallel aggregation over a bigint column.

Back-patch of commit 751804998.  The code known to be affected only exists
in 9.6 and later, but we do have some stuff using int128 in 9.5, so patch
back to 9.5.

Discussion: https://postgr.es/m/20171110185747[email protected]

config/c-compiler.m4
configure
configure.in
src/include/c.h
src/include/pg_config.h.in
src/include/pg_config.h.win32
src/test/regress/expected/select_parallel.out
src/test/regress/sql/select_parallel.sql

index 7275ea69fef7c4c2f0e92de45ed73280c33af5cd..8d9844abc6d15758560868efe997792561d346b4 100644 (file)
@@ -96,9 +96,11 @@ undefine([Ac_cachevar])dnl
 # PGAC_TYPE_128BIT_INT
 # ---------------------
 # Check if __int128 is a working 128 bit integer type, and if so
-# define PG_INT128_TYPE to that typename.  This currently only detects
-# a GCC/clang extension, but support for different environments may be
-# added in the future.
+# define PG_INT128_TYPE to that typename, and define ALIGNOF_PG_INT128_TYPE
+# as its alignment requirement.
+#
+# This currently only detects a GCC/clang extension, but support for other
+# environments may be added in the future.
 #
 # For the moment we only test for support for 128bit math; support for
 # 128bit literals and snprintf is not required.
@@ -128,6 +130,7 @@ return 1;
 [pgac_cv__128bit_int=no])])
 if test x"$pgac_cv__128bit_int" = xyes ; then
   AC_DEFINE(PG_INT128_TYPE, __int128, [Define to the name of a signed 128-bit integer type.])
+  AC_CHECK_ALIGNOF(PG_INT128_TYPE)
 fi])# PGAC_TYPE_128BIT_INT
 
 
index d9298bf531195a51d828f3a04f07c1db8dc5d73e..883e1ba0af92bc3ee228ba6ec4310745d84c7233 100755 (executable)
--- a/configure
+++ b/configure
@@ -14806,7 +14806,10 @@ _ACEOF
 
 # Compute maximum alignment of any basic type.
 # We assume long's alignment is at least as strong as char, short, or int;
-# but we must check long long (if it exists) and double.
+# but we must check long long (if it is being used for int64) and double.
+# 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_long
 if test $MAX_ALIGNOF -lt $ac_cv_alignof_double ; then
@@ -14866,7 +14869,7 @@ _ACEOF
 fi
 
 
-# Check for extensions offering the integer scalar type __int128.
+# Some compilers offer a 128-bit integer scalar type.
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __int128" >&5
 $as_echo_n "checking for __int128... " >&6; }
 if ${pgac_cv__128bit_int+:} false; then :
@@ -14916,6 +14919,41 @@ if test x"$pgac_cv__128bit_int" = xyes ; then
 
 $as_echo "#define PG_INT128_TYPE __int128" >>confdefs.h
 
+  # 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 PG_INT128_TYPE" >&5
+$as_echo_n "checking alignment of PG_INT128_TYPE... " >&6; }
+if ${ac_cv_alignof_PG_INT128_TYPE+:} 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_PG_INT128_TYPE"        "$ac_includes_default
+#ifndef offsetof
+# define offsetof(type, member) ((char *) &((type *) 0)->member - (char *) 0)
+#endif
+typedef struct { char x; PG_INT128_TYPE y; } ac__type_alignof_;"; then :
+
+else
+  if test "$ac_cv_type_PG_INT128_TYPE" = 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 PG_INT128_TYPE
+See \`config.log' for more details" "$LINENO" 5; }
+   else
+     ac_cv_alignof_PG_INT128_TYPE=0
+   fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_alignof_PG_INT128_TYPE" >&5
+$as_echo "$ac_cv_alignof_PG_INT128_TYPE" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define ALIGNOF_PG_INT128_TYPE $ac_cv_alignof_PG_INT128_TYPE
+_ACEOF
+
+
 fi
 
 # Check for various atomic operations now that we have checked how to declare
index 1675b3ba02a46454aa3578a0a2df95d821a2e974..ffe25635c8da8a7efbbe1a61224017b399484471 100644 (file)
@@ -1832,7 +1832,10 @@ AC_CHECK_ALIGNOF(double)
 
 # Compute maximum alignment of any basic type.
 # We assume long's alignment is at least as strong as char, short, or int;
-# but we must check long long (if it exists) and double.
+# but we must check long long (if it is being used for int64) and double.
+# 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_long
 if test $MAX_ALIGNOF -lt $ac_cv_alignof_double ; then
@@ -1849,7 +1852,7 @@ AC_DEFINE_UNQUOTED(MAXIMUM_ALIGNOF, $MAX_ALIGNOF, [Define as the maximum alignme
 AC_CHECK_TYPES([int8, uint8, int64, uint64], [], [],
 [#include ])
 
-# Check for extensions offering the integer scalar type __int128.
+# Some compilers offer a 128-bit integer scalar type.
 PGAC_TYPE_128BIT_INT
 
 # Check for various atomic operations now that we have checked how to declare
index 38c0ed7c304e8f7db281cc0232aa2967de1abe20..d4dd86636f72d62fee6ffafe004221bf20d4ac15 100644 (file)
@@ -370,13 +370,29 @@ typedef unsigned long long int uint64;
 
 /*
  * 128-bit signed and unsigned integers
- *     There currently is only a limited support for the type. E.g. 128bit
- *     literals and snprintf are not supported; but math is.
+ *     There currently is only limited support for such types.
+ *     E.g. 128bit literals and snprintf are not supported; but math is.
+ *     Also, because we exclude such types when choosing MAXIMUM_ALIGNOF,
+ *     it must be possible to coerce the compiler to allocate them on no
+ *     more than MAXALIGN boundaries.
  */
 #if defined(PG_INT128_TYPE)
-#define HAVE_INT128
-typedef PG_INT128_TYPE int128;
-typedef unsigned PG_INT128_TYPE uint128;
+#if defined(pg_attribute_aligned) || ALIGNOF_PG_INT128_TYPE <= MAXIMUM_ALIGNOF
+#define HAVE_INT128 1
+
+typedef PG_INT128_TYPE int128
+#if defined(pg_attribute_aligned)
+pg_attribute_aligned(MAXIMUM_ALIGNOF)
+#endif
+;
+
+typedef unsigned PG_INT128_TYPE uint128
+#if defined(pg_attribute_aligned)
+pg_attribute_aligned(MAXIMUM_ALIGNOF)
+#endif
+;
+
+#endif
 #endif
 
 /*
index c65dd7db21504901b6f8d25c3a7f4c5caff96763..f54bbedac5a1923dbfe910ae139ad1281d79ab06 100644 (file)
@@ -27,6 +27,9 @@
 /* The normal alignment of `long long int', in bytes. */
 #undef ALIGNOF_LONG_LONG_INT
 
+/* The normal alignment of `PG_INT128_TYPE', in bytes. */
+#undef ALIGNOF_PG_INT128_TYPE
+
 /* The normal alignment of `short', in bytes. */
 #undef ALIGNOF_SHORT
 
index 9a876f5f0adabb078b3f53c075f35523bcfcbb69..a5cecb3e07d36a828d8d538d81240447982a787e 100644 (file)
@@ -34,6 +34,9 @@
 /* The alignment requirement of a `long long int'. */
 #define ALIGNOF_LONG_LONG_INT 8
 
+/* The normal alignment of `PG_INT128_TYPE', in bytes. */
+#undef ALIGNOF_PG_INT128_TYPE
+
 /* The alignment requirement of a `short'. */
 #define ALIGNOF_SHORT 2
 
index f3e8b565871b62f852160df1be772dd89709f66f..ecabd35cca6573cecc08192f350c65b5296ce0f5 100644 (file)
@@ -444,6 +444,24 @@ select string4 from tenk1 order by string4 limit 5;
 
 reset max_parallel_workers;
 reset enable_hashagg;
+-- check parallelized int8 aggregate (bug #14897)
+explain (costs off)
+select avg(unique1::int8) from tenk1;
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Finalize Aggregate
+   ->  Gather
+         Workers Planned: 4
+         ->  Partial Aggregate
+               ->  Parallel Index Only Scan using tenk1_unique1 on tenk1
+(5 rows)
+
+select avg(unique1::int8) from tenk1;
+          avg          
+-----------------------
+ 4999.5000000000000000
+(1 row)
+
 -- test the sanity of parallel query after the active role is dropped.
 drop role if exists regress_parallel_worker;
 NOTICE:  role "regress_parallel_worker" does not exist, skipping
index d2eee147e99e55d8c36e37e79de78dcbb8ef677a..280c78765362bd44cdba3daf33ca47823a1764ea 100644 (file)
@@ -168,6 +168,12 @@ select string4 from tenk1 order by string4 limit 5;
 reset max_parallel_workers;
 reset enable_hashagg;
 
+-- check parallelized int8 aggregate (bug #14897)
+explain (costs off)
+select avg(unique1::int8) from tenk1;
+
+select avg(unique1::int8) from tenk1;
+
 -- test the sanity of parallel query after the active role is dropped.
 drop role if exists regress_parallel_worker;
 create role regress_parallel_worker;