get_fpu_state and set_fpu_state take a GFC_FPE_STATE_BUFFER_SIZE buffer
that is a character array: either the front end's fpstate temporary
created by gfc_save_fp_state, or the hidden component of IEEE_STATUS_TYPE
passed by IEEE_GET_STATUS / IEEE_SET_STATUS.  Neither has more than byte
alignment, but every fpu-*.h implementation used the buffer directly as
the target's fenv_t (or its own state struct).  On strict-alignment
targets that is a misaligned access and faults, e.g. the SIGBUS in
__fegetenv for ieee_6.f90 at -O0 on SPARC.

Giving the buffer a stricter type instead (an integer array rather than
a character array) would change the alignment, and so the layout, of
any user derived type that contains an IEEE_STATUS_TYPE.

So build the state in a properly aligned local and memcpy it to or from
the caller's buffer, in all six implementations (glibc, aix, macppc, 387,
aarch64, sysv).  This covers both callers on every target and leaves the
buffer layout unchanged.

The new test places two IEEE_STATUS_TYPE components at offsets 1 and 34
of a derived type, so at least one of them is misaligned for any state
type that needs more than byte alignment.

        PR fortran/82968

libgfortran/ChangeLog:

        * libgfortran.h (get_fpu_state, set_fpu_state): Document that the
        buffer has no particular alignment.
        * config/fpu-387.h (get_fpu_state, set_fpu_state): Go through an
        aligned local struct fenv.
        * config/fpu-aarch64.h (get_fpu_state, set_fpu_state): Likewise.
        * config/fpu-aix.h (get_fpu_state, set_fpu_state): Go through an
        aligned local fenv_t.
        * config/fpu-glibc.h (get_fpu_state, set_fpu_state): Likewise.
        * config/fpu-macppc.h (get_fpu_state, set_fpu_state): Likewise.
        * config/fpu-sysv.h (get_fpu_state, set_fpu_state): Go through an
        aligned local fpu_state_t.

gcc/testsuite/ChangeLog:

        * gfortran.dg/ieee/pr82968.f90: New test.

Signed-off-by: Igor Shevlyakov <[email protected]>
---
Bootstrapped and regtested on x86_64-pc-linux-gnu (-m32/-m64), no new 
regressions.

 gcc/testsuite/gfortran.dg/ieee/pr82968.f90 | 35 ++++++++++++++++++++++
 libgfortran/config/fpu-387.h               | 18 ++++++-----
 libgfortran/config/fpu-aarch64.h           | 14 +++++----
 libgfortran/config/fpu-aix.h               | 10 +++++--
 libgfortran/config/fpu-glibc.h             | 10 +++++--
 libgfortran/config/fpu-macppc.h            | 10 +++++--
 libgfortran/config/fpu-sysv.h              | 18 ++++++-----
 libgfortran/libgfortran.h                  |  2 ++
 8 files changed, 90 insertions(+), 27 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/ieee/pr82968.f90

diff --git a/gcc/testsuite/gfortran.dg/ieee/pr82968.f90 
b/gcc/testsuite/gfortran.dg/ieee/pr82968.f90
new file mode 100644
index 00000000000..781654d21c3
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/ieee/pr82968.f90
@@ -0,0 +1,35 @@
+! { dg-do run }
+! PR fortran/82968
+! Misaligned IEEE_STATUS_TYPE components must not fault.
+
+  use :: ieee_arithmetic
+  implicit none
+
+  type :: holder
+    character(len=1) :: pad1
+    type(ieee_status_type) :: s1
+    character(len=1) :: pad2
+    type(ieee_status_type) :: s2
+  end type
+
+  type(holder) :: h
+  type(ieee_round_type) :: mode
+  real :: x
+
+  if (.not. (ieee_support_rounding (ieee_down, x) &
+             .and. ieee_support_rounding (ieee_up, x))) stop
+
+  call ieee_set_rounding_mode (ieee_down)
+  call ieee_get_status (h%s1)
+  call ieee_set_rounding_mode (ieee_up)
+  call ieee_get_status (h%s2)
+  call ieee_set_rounding_mode (ieee_nearest)
+
+  call ieee_set_status (h%s1)
+  call ieee_get_rounding_mode (mode)
+  if (mode /= ieee_down) stop 1
+
+  call ieee_set_status (h%s2)
+  call ieee_get_rounding_mode (mode)
+  if (mode /= ieee_up) stop 2
+end
diff --git a/libgfortran/config/fpu-387.h b/libgfortran/config/fpu-387.h
index 710105195db..a527c01e28e 100644
--- a/libgfortran/config/fpu-387.h
+++ b/libgfortran/config/fpu-387.h
@@ -429,29 +429,33 @@ support_fpu_rounding_mode (int mode)
 void
 get_fpu_state (void *state)
 {
-  struct fenv *envp = state;
+  struct fenv env;
 
-  __asm__ __volatile__ ("fnstenv\t%0" : "=m" (*envp));
+  __asm__ __volatile__ ("fnstenv\t%0" : "=m" (env));
 
   /* fnstenv has the side effect of masking all exceptions, so we need
      to restore the control word after that.  */
-  __asm__ __volatile__ ("fldcw\t%0" : : "m" (envp->__control_word));
+  __asm__ __volatile__ ("fldcw\t%0" : : "m" (env.__control_word));
 
   if (has_sse())
-    __asm__ __volatile__ ("%vstmxcsr\t%0" : "=m" (envp->__mxcsr));
+    __asm__ __volatile__ ("%vstmxcsr\t%0" : "=m" (env.__mxcsr));
+
+  memcpy (state, &env, sizeof (env));
 }
 
 void
 set_fpu_state (void *state)
 {
-  struct fenv *envp = state;
+  struct fenv env;
+
+  memcpy (&env, state, sizeof (env));
 
   /* glibc sources (sysdeps/x86_64/fpu/fesetenv.c) do something more
      complex than this, but I think it suffices in our case.  */
-  __asm__ __volatile__ ("fldenv\t%0" : : "m" (*envp));
+  __asm__ __volatile__ ("fldenv\t%0" : : "m" (env));
 
   if (has_sse())
-    __asm__ __volatile__ ("%vldmxcsr\t%0" : : "m" (envp->__mxcsr));
+    __asm__ __volatile__ ("%vldmxcsr\t%0" : : "m" (env.__mxcsr));
 }
 
 
diff --git a/libgfortran/config/fpu-aarch64.h b/libgfortran/config/fpu-aarch64.h
index 5a4db9ee003..d61bb2065d0 100644
--- a/libgfortran/config/fpu-aarch64.h
+++ b/libgfortran/config/fpu-aarch64.h
@@ -223,18 +223,20 @@ set_fpu_except_flags (int set, int clear)
 void
 get_fpu_state (void *state)
 {
-  struct fenv *envp = state;
-  envp->__fpcr = __builtin_aarch64_get_fpcr();
-  envp->__fpsr = __builtin_aarch64_get_fpsr();
+  struct fenv env;
+  env.__fpcr = __builtin_aarch64_get_fpcr();
+  env.__fpsr = __builtin_aarch64_get_fpsr();
+  memcpy (state, &env, sizeof (env));
 }
 
 
 void
 set_fpu_state (void *state)
 {
-  struct fenv *envp = state;
-  __builtin_aarch64_set_fpcr(envp->__fpcr);
-  __builtin_aarch64_set_fpsr(envp->__fpsr);
+  struct fenv env;
+  memcpy (&env, state, sizeof (env));
+  __builtin_aarch64_set_fpcr(env.__fpcr);
+  __builtin_aarch64_set_fpsr(env.__fpsr);
 }
 
 
diff --git a/libgfortran/config/fpu-aix.h b/libgfortran/config/fpu-aix.h
index a549576aa86..08f5ac95ac8 100644
--- a/libgfortran/config/fpu-aix.h
+++ b/libgfortran/config/fpu-aix.h
@@ -426,13 +426,19 @@ support_fpu_rounding_mode (int mode)
 void
 get_fpu_state (void *state)
 {
-  fegetenv (state);
+  fenv_t env;
+
+  fegetenv (&env);
+  memcpy (state, &env, sizeof (env));
 }
 
 void
 set_fpu_state (void *state)
 {
-  fesetenv (state);
+  fenv_t env;
+
+  memcpy (&env, state, sizeof (env));
+  fesetenv (&env);
 }
 
 
diff --git a/libgfortran/config/fpu-glibc.h b/libgfortran/config/fpu-glibc.h
index 1ce2da8c953..93fb920fc9e 100644
--- a/libgfortran/config/fpu-glibc.h
+++ b/libgfortran/config/fpu-glibc.h
@@ -447,14 +447,20 @@ support_fpu_rounding_mode (int mode)
 void
 get_fpu_state (void *state)
 {
-  fegetenv (state);
+  fenv_t env;
+
+  fegetenv (&env);
+  memcpy (state, &env, sizeof (env));
 }
 
 
 void
 set_fpu_state (void *state)
 {
-  fesetenv (state);
+  fenv_t env;
+
+  memcpy (&env, state, sizeof (env));
+  fesetenv (&env);
 }
 
 
diff --git a/libgfortran/config/fpu-macppc.h b/libgfortran/config/fpu-macppc.h
index c9f190e0aa5..190052c2c37 100644
--- a/libgfortran/config/fpu-macppc.h
+++ b/libgfortran/config/fpu-macppc.h
@@ -322,12 +322,18 @@ void set_fpu_except_flags (int set, int clear)
 
 void get_fpu_state (void *state)
 {
-  fegetenv (state);
+  fenv_t env;
+
+  fegetenv (&env);
+  memcpy (state, &env, sizeof (env));
 }
 
 void set_fpu_state (void *state)
 {
-  fesetenv (state);
+  fenv_t env;
+
+  memcpy (&env, state, sizeof (env));
+  fesetenv (&env);
 }
 
 int get_fpu_rounding_mode (void)
diff --git a/libgfortran/config/fpu-sysv.h b/libgfortran/config/fpu-sysv.h
index 1e0ca8342ca..8d7ecbb5b28 100644
--- a/libgfortran/config/fpu-sysv.h
+++ b/libgfortran/config/fpu-sysv.h
@@ -399,21 +399,23 @@ _Static_assert (sizeof(fpu_state_t) <= (size_t) 
GFC_FPE_STATE_BUFFER_SIZE,
 void
 get_fpu_state (void *s)
 {
-  fpu_state_t *state = s;
+  fpu_state_t state;
 
-  state->mask = fpgetmask ();
-  state->sticky = fpgetsticky ();
-  state->round = fpgetround ();
+  state.mask = fpgetmask ();
+  state.sticky = fpgetsticky ();
+  state.round = fpgetround ();
+  memcpy (s, &state, sizeof (state));
 }
 
 void
 set_fpu_state (void *s)
 {
-  fpu_state_t *state = s;
+  fpu_state_t state;
 
-  fpsetmask (state->mask);
-  FPSETSTICKY (state->sticky);
-  fpsetround (state->round);
+  memcpy (&state, s, sizeof (state));
+  fpsetmask (state.mask);
+  FPSETSTICKY (state.sticky);
+  fpsetround (state.round);
 }
 
 
diff --git a/libgfortran/libgfortran.h b/libgfortran/libgfortran.h
index 3613b5d68c0..5a170a3595a 100644
--- a/libgfortran/libgfortran.h
+++ b/libgfortran/libgfortran.h
@@ -905,6 +905,8 @@ internal_proto(get_fpu_rounding_mode);
 extern int support_fpu_rounding_mode (int);
 internal_proto(support_fpu_rounding_mode);
 
+/* Save and restore the FPU state in a GFC_FPE_STATE_BUFFER_SIZE buffer,
+   which has no particular alignment.  */
 extern void get_fpu_state (void *);
 internal_proto(get_fpu_state);
 
-- 
2.55.0


Reply via email to