@@@ fltfmt mess
[mLib] / m4 / mdw-probe-fltfmt.m4
diff --git a/m4/mdw-probe-fltfmt.m4 b/m4/mdw-probe-fltfmt.m4
new file mode 100644 (file)
index 0000000..736e871
--- /dev/null
@@ -0,0 +1,310 @@
+dnl -*-autoconf-*-
+
+### SYNOPSIS
+###
+###   mdw_PROBE_FLTFMT
+###
+### DESCRIPTION
+###
+###   Attempts to determine the target system's floating-point formats.
+###   The macros `FLT_FORMAT', `DBL_FORMAT', and `LDBL_FORMAT' are defined if
+###   the corresponding formats are recognized.  The values of these macros
+###   are a bitmask:
+###
+###    * `FLTFMT_ORGMASK' is a bitmask covering the `organization' field,
+###      which identifies the organization which defined the format.
+###
+###        -- `FLTFMT_IEEE' identifies IEEE 754.
+###        -- `FLTFMT_INTEL' identifies Intel.
+###
+###     * `FLTFMT_TYPEMASK' is a bitmask covering the `type' field, which
+###      must be interpreted together with the organization field to
+###      determine the format.
+###
+###        -- `FLTFMT_IEEE_F32' is the IEEE 754 `binary32' format.
+###        -- `FLTFMT_IEEE_F64' is the IEEE 754 `binary64' format.
+###        -- `FLTFMT_IEEE_F128' is the IEEE 754 `binary128' format.
+###        -- `FLTFMT_INTEL_F80' is the Intel x87 80-bit double-extended
+###           format.
+###
+###     * `FLTFMT_ENDMASK' is a bitmask covering the `endian' field, which
+###      describes the byte ordering convention used.
+###
+###        -- `FLTFMT_LE' means little-endian format.
+###
+###        -- `FLTFMT_BE' means big-endian format.
+###
+###        -- `FLTFMT_ARME' means `Acorn's ridiculous mixed-endian' format,
+###           used by the ARM FPA, which stored the a `binary64' value as
+###           two 32-bit words, most significant word first, but with the
+###           individual words stored in little-endian byte order.
+###
+###      Other conventions exist and support for them may be added later.
+###
+###   Finally, there are predefined values for relevant combinations of
+###   format codes and byte orderings:
+###
+###     * `FLTFMT_IEEE_F32_LE' and `FLTFMT_IEEE_F32_BE';
+###     * `FLTFMT_IEEE_F64_LE' and `FLTFMT_IEEE_F64_BE';
+###     * `FLTFMT_IEEE_F128_LE' and `FLTFMT_IEEE_F128_BE';
+###     * `FLTFMT_INTEL_F80_LE' and `FLTFMT_INTEL_F80_BE'.
+###
+###   (I don't know of anything which uses Intel's double-extended format in
+###   big-endian order, but it was easy enough to check for.  The IEEE
+###   formats are used on ARM64 and z/Architecture with opposite byte order.)
+###
+###   This macro works correctly when cross-compiling.
+###
+### LICENSE
+###
+###   Copyright (c) 2024 Mark Wooding <mdw@distorted.org.uk>
+###
+###   This program is free software: you can redistribute it and/or modify it
+###   under the terms of the GNU General Public License as published by the
+###   Free Software Foundation, either version 2 of the License, or (at your
+###   option) any later version.
+###
+###   This program 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
+###   General Public License for more details.
+###
+###   You should have received a copy of the GNU General Public License along
+###   with this program. If not, see <http://www.gnu.org/licenses/>.
+###
+###   In particular, no exception to the GPL is granted regarding generated
+###   `configure' scripts which are the output of Autoconf.
+
+dnl Principle of operation:
+dnl
+dnl The essential trick here lies in finding floating-point numbers whose
+dnl encoding, in various formats of interest, happen to be recognizable
+dnl diagnostic text strings.  The structure definitions provide some space
+dnl for framing text which allows us to scrape the resulting diagnostic
+dnl strings from the object file.
+dnl
+dnl IEEE formats conveniently don't impose any restrictions on the contents
+dnl of the fraction field because there's a hidden bit.  The Intel x87
+dnl double-extended format makes the most significant bit explicit, and also
+dnl expects normalization, which means that the top bit of the fraction bytes
+dnl must be set, so everything gets quite ugly.  Worse, the actual data is 10
+dnl bytes long, but it sits in a 16-byte field to force alignment in vectors,
+dnl with the result that there are unavoidably zero bytes in our diagnostic
+dnl output.  Different tools respond to these differently; e.g., GNU sed(1)
+dnl just writes out the zero bytes like they were any other character, while
+dnl Busybox sed(1) terminates the output line.  As a result, we have to be
+dnl rather more flexible about matching this than I'd really like.  (Messing
+dnl about with compiler-specific hacks for structure packing won't help here
+dnl because the analysis code still has to cope with compilers which don't
+dnl have those hacks.)
+
+# Serial 1
+AC_COPYRIGHT([
+Portions copyright (c) 2024 Mark Wooding.
+
+This configure script is free software: you can redistribute it and/or
+modify it under he terms of the GNU General Public License as published
+by the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.])
+
+AC_DEFUN([mdw_PROBE_FLTFMT],
+  [AC_CACHE_CHECK([floating-point representations], [mdw_cv_fltfmt],
+     [mdw_fltfmt=nil mdw_dblfmt=nil mdw_ldblfmt=nil
+      AC_LINK_IFELSE([AC_LANG_SOURCE([
+
+/* The following program is copyright (c) 2024 Mark Wooding.  It is free
+ * software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation,
+ * either version 2 of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+@%:@include <float.h>
+
+@%:@define DEFFLTDIAG(ty, type)                                                \
+  struct fltdiag_@%:@@%:@ty { char top@<:@48@:>@; type x; char tail@<:@4@:>@; }
+
+DEFFLTDIAG(flt, float);
+
+static const struct fltdiag_flt flt_ieee_f32 = {
+  "\0\0\0\0\0\0\0\0\n@@@ mdw-probe-fltfmt float ieee-f32 = >",
+  781.0352,
+  "<\n\0\0"
+};
+@%:@define DO_FLT_IEEE_F32 DO(flt_ieee_f32)
+
+DEFFLTDIAG(dbl, double);
+
+@%:@if DBL_MAX_10_EXP > 40
+static const struct fltdiag_dbl dbl_ieee_f64 = {
+  "\0\0\0\0\0\0\0\n@@@ mdw-probe-fltfmt double ieee-f64 = >",
+  1.5839800103804824e40,
+  "<\n\0\0"
+};
+@%:@  define DO_DBL_IEEE_F64 DO(dbl_ieee_f64)
+@%:@else
+@%:@  define DO_DBL_IEEE_F64
+@%:@endif
+
+DEFFLTDIAG(ldbl, long double);
+
+@%:@if LDBL_MAX_10_EXP > 40
+static const struct fltdiag_ldbl ldbl_ieee_f64 = {
+  "\0\0\n@@@ mdw-probe-fltfmt long-double ieee-f64 = >",
+  1.5839800103804824e40,
+  "<\n\0\0"
+};
+@%:@  define DO_LDBL_IEEE_F64 DO(ldbl_ieee_f64)
+@%:@else
+@%:@  define DO_LDBL_IEEE_F64
+@%:@endif
+
+@%:@if LDBL_MAX_10_EXP > 1257
+static const struct fltdiag_ldbl ldbl_ieee_f128 = {
+  "\0\n@@@ mdw-probe-fltfmt long-double ieee-f128 = >",
+  1.6487728650847311136108983312706536e+1257L,
+  "<\n\0\0"
+};
+@%:@  define DO_LDBL_IEEE_F128 DO(ldbl_ieee_f128)
+@%:@else
+@%:@  define DO_LDBL_IEEE_F128
+@%:@endif
+
+@%:@if LDBL_MAX_10_EXP > 793
+static const struct fltdiag_ldbl ldbl_intel_f80 = {
+  "\0\n@@@ mdw-probe-fltfmt long-double intel-f80 = >",
+  1.2806567921142816197e+793L,
+  "<\n\0\0"
+};
+@%:@  define DO_LDBL_INTEL_F80 DO(ldbl_intel_f80)
+@%:@else
+@%:@  define DO_LDBL_INTEL_F80
+@%:@endif
+
+@%:@include <stdio.h>
+int main(void)
+{
+@%:@define DO(var) fwrite(&var, sizeof(var), 1, stdout)
+  DO_FLT_IEEE_F32;
+  DO_DBL_IEEE_F64;
+  DO_LDBL_IEEE_F64;
+  DO_LDBL_IEEE_F128;
+  DO_LDBL_IEEE_F128;
+  DO_LDBL_INTEL_F80;
+@%:@undef DO
+  return (0);
+}
+])],
+       [sed -n "/^@@@ mdw-probe-fltfmt @<:@^ @:>@* @<:@^ @:>@* = >/p" \
+                conftest$EXEEXT >conftest.out
+        while read _at _tag ty fmt _eq diag; do
+          case $ty,$fmt,$diag in
+            "float,ieee-f32,>ABCD<") mdw_fltfmt=ieee-f32-le ;;
+            "float,ieee-f32,>DCBA<") mdw_fltfmt=ieee-f32-be ;;
+            "double,ieee-f64,>ABCDEFGH<") mdw_dblfmt=ieee-f64-le ;;
+            "double,ieee-f64,>EFGHABCD<") mdw_dblfmt=ieee-f64-arme ;;
+            "double,ieee-f64,>HGFEDCBA<") mdw_dblfmt=ieee-f64-be ;;
+            "long-double,ieee-f64,>ABCDEFGH<") mdw_ldblfmt=ieee-f64-le ;;
+            "long-double,ieee-f64,>HGFEDCBA<") mdw_ldblfmt=ieee-f64-be ;;
+            "long-double,ieee-f128,>ABCDEFGHIJKLMNOP<")
+              mdw_ldblfmt=ieee-f128-le ;;
+            "long-double,ieee-f128,>PONMLKJIHGFEDCBA<")
+              mdw_ldblfmt=ieee-f128-be ;;
+            "long-double,intel-f80,>ABCDEFGÈIJ" | \
+            "long-double,intel-f80,>ABCDEFGÈIJ"*"<")
+              mdw_ldblfmt=intel-f80-le ;;
+            "long-double,intel-f80,>JIÈGFEDCBA" | \
+            "long-double,intel-f80,>JIÈGFEDCBA"*"<")
+              mdw_ldblfmt=intel-f80-be ;;
+          esac
+        done <conftest.out])
+      mdw_cv_fltfmt="float=$mdw_fltfmt"
+      mdw_cv_fltfmt="$mdw_cv_fltfmt double=$mdw_dblfmt"
+      mdw_cv_fltfmt="$mdw_cv_fltfmt long-double=$mdw_ldblfmt"])
+
+   AC_DEFINE([FLTFMT_ENDMASK], [0x0300],
+       [mask for byte ordering])
+   AC_DEFINE([FLTFMT_LE], [0x0000],
+       [little-endian floating-point storage])
+   AC_DEFINE([FLTFMT_BE], [0x0100],
+       [big-endian floating-point storage])
+   AC_DEFINE([FLTFMT_ARME], [0x0200],
+       [Acorn's ridiculous mixed-endian floating-point storage])
+
+   AC_DEFINE([FLTFMT_ORGMASK], [0xfc00],
+       [mask for floating-point organization id])
+   AC_DEFINE([FLTFMT_TYPEMASK], [0x00ff],
+       [mask for floating-point format type])
+
+   AC_DEFINE([FLTFMT_UNKNOWN], [0x0000],
+       [unrecognized floating-point format])
+
+   AC_DEFINE([FLTFMT_IEEE], [0x0400],
+       [floating-point organization id for IEEE 754])
+   AC_DEFINE([FLTFMT_IEEE_F32], [FLTFMT_IEEE | 5],
+       [IEEE 754 `binary32' format])
+   AC_DEFINE([FLTFMT_IEEE_F32_LE], [(FLTFMT_IEEE_F32 | FLTFMT_LE)],
+       [IEEE `binary32' format, little-endian])
+   AC_DEFINE([FLTFMT_IEEE_F32_BE], [(FLTFMT_IEEE_F32 | FLTFMT_BE)],
+       [IEEE `binary32' format, big-endian])
+   AC_DEFINE([FLTFMT_IEEE_F64], [FLTFMT_IEEE | 6],
+       [IEEE 754 `binary32' format])
+   AC_DEFINE([FLTFMT_IEEE_F64_LE], [(FLTFMT_IEEE_F64 | FLTFMT_LE)],
+       [IEEE `binary64' format, little-endian])
+   AC_DEFINE([FLTFMT_IEEE_F64_ARME], [(FLTFMT_IEEE_F64 | FLTFMT_ARME)],
+       [IEEE `binary64' format, Acorn ridiculous mixed-endian])
+   AC_DEFINE([FLTFMT_IEEE_F64_BE], [(FLTFMT_IEEE_F64 | FLTFMT_BE)],
+       [IEEE `binary64' format, big-endian])
+   AC_DEFINE([FLTFMT_IEEE_F128], [FLTFMT_IEEE | 7],
+       [IEEE 754 `binary32' format])
+   AC_DEFINE([FLTFMT_IEEE_F128_LE], [(FLTFMT_IEEE_F128 | FLTFMT_LE)],
+       [IEEE `binary128' format, little-endian])
+   AC_DEFINE([FLTFMT_IEEE_F128_BE], [(FLTFMT_IEEE_F128 | FLTFMT_BE)],
+       [IEEE `binary128' format, big-endian])
+
+   AC_DEFINE([FLTFMT_INTEL], [0x0800],
+       [floating-point organization id for Intel])
+   AC_DEFINE([FLTFMT_INTEL_F80], [FLTFMT_INTEL | 80],
+       [Intel x87 double-extended format])
+   AC_DEFINE([FLTFMT_INTEL_F80_LE], [(FLTFMT_INTEL_F80 | FLTFMT_LE)],
+       [Intel x86 double-extended format, little-endian])
+   AC_DEFINE([FLTFMT_INTEL_F80_BE], [(FLTFMT_INTEL_F80 | FLTFMT_BE)],
+       [Intel x87 double-extended format, big-endian])
+
+   AH_TEMPLATE([FLT_FORMAT],
+       [floating point format for `float' type])
+   AH_TEMPLATE([DBL_FORMAT],
+       [floating point format for `double' type])
+   AH_TEMPLATE([LDBL_FORMAT],
+       [floating point format for `long double' type])
+
+   for w in $mdw_cv_fltfmt; do
+     ty=${w%=*} fmt=${w@%:@*=}
+     case $ty in
+       float) var=FLT ;;
+       double) var=DBL ;;
+       long-double) var=LDBL ;;
+       *) AC_MSG_ERROR([unexpected floating-point type \`$ty']) ;;
+     esac
+     case $fmt in
+       nil) value=UNKNOWN ;;
+       ieee-f32-le) value=IEEE_F32_LE ;;
+       ieee-f32-be) value=IEEE_F32_BE ;;
+       ieee-f64-le) value=IEEE_F64_LE ;;
+       ieee-f64-arme) value=IEEE_F64_ARME ;;
+       ieee-f64-be) value=IEEE_F64_BE ;;
+       ieee-f128-le) value=IEEE_F128_LE ;;
+       ieee-f128-be) value=IEEE_F128_BE ;;
+       intel-f80-le) value=INTEL_F80_LE ;;
+       intel-f80-be) value=INTEL_F80_BE ;;
+       *) AC_MSG_ERROR([unexpected floating-point format \`$fmt']) ;;
+     esac
+     AC_DEFINE_UNQUOTED([${var}_FORMAT], [FLTFMT_$value])
+   done])