diff --git a/make/test/JtregNativeHotspot.gmk b/make/test/JtregNativeHotspot.gmk index 45efa033cff..24fcac2198e 100644 --- a/make/test/JtregNativeHotspot.gmk +++ b/make/test/JtregNativeHotspot.gmk @@ -851,6 +851,7 @@ ifeq ($(call isTargetOs, linux), true) BUILD_TEST_exeinvoke_exeinvoke.c_OPTIMIZATION := NONE BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exeFPRegs := -ldl BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libAsyncGetCallTraceTest := -ldl + BUILD_HOTSPOT_JTREG_LIBRARIES_LDFLAGS_libfast-math := -ffast-math else BUILD_HOTSPOT_JTREG_EXCLUDE += libtest-rw.c libtest-rwx.c \ exeinvoke.c exestack-gap.c exestack-tls.c libAsyncGetCallTraceTest.cpp diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index 8c8cd2166f0..ddfd3a5ec78 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -78,6 +78,7 @@ # include # include # include +# include # include # include # include @@ -975,6 +976,41 @@ bool os::dll_address_to_library_name(address addr, char* buf, // in case of error it checks if .dll/.so was built for the // same architecture as Hotspot is running on +void *os::Bsd::dlopen_helper(const char *filename, int mode) { +#ifndef IA32 + // Save and restore the floating-point environment around dlopen(). + // There are known cases where global library initialization sets + // FPU flags that affect computation accuracy, for example, enabling + // Flush-To-Zero and Denormals-Are-Zero. Do not let those libraries + // break Java arithmetic. Unfortunately, this might affect libraries + // that might depend on these FPU features for performance and/or + // numerical "accuracy", but we need to protect Java semantics first + // and foremost. See JDK-8295159. + + // This workaround is ineffective on IA32 systems because the MXCSR + // register (which controls flush-to-zero mode) is not stored in the + // legacy fenv. + + fenv_t default_fenv; + int rtn = fegetenv(&default_fenv); + assert(rtn == 0, "fegetenv must succeed"); +#endif // IA32 + + void * result= ::dlopen(filename, RTLD_LAZY); + +#ifndef IA32 + if (result != nullptr && ! IEEE_subnormal_handling_OK()) { + // We just dlopen()ed a library that mangled the floating-point + // flags. Silently fix things now. + int rtn = fesetenv(&default_fenv); + assert(rtn == 0, "fesetenv must succeed"); + assert(IEEE_subnormal_handling_OK(), "fsetenv didn't work"); + } +#endif // IA32 + + return result; +} + #ifdef __APPLE__ void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { #ifdef STATIC_BUILD @@ -984,7 +1020,7 @@ void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { void* result; JFR_ONLY(NativeLibraryLoadEvent load_event(filename, &result);) - result = ::dlopen(filename, RTLD_LAZY); + result = os::Bsd::dlopen_helper(filename, RTLD_LAZY); if (result != nullptr) { Events::log_dll_message(nullptr, "Loaded shared library %s", filename); // Successful loading @@ -1017,7 +1053,7 @@ void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { void* result; JFR_ONLY(NativeLibraryLoadEvent load_event(filename, &result);) - result = ::dlopen(filename, RTLD_LAZY); + result = os::Bsd::dlopen_helper(filename, RTLD_LAZY); if (result != nullptr) { Events::log_dll_message(nullptr, "Loaded shared library %s", filename); // Successful loading diff --git a/src/hotspot/os/bsd/os_bsd.hpp b/src/hotspot/os/bsd/os_bsd.hpp index d34803e144b..f79212bc43c 100644 --- a/src/hotspot/os/bsd/os_bsd.hpp +++ b/src/hotspot/os/bsd/os_bsd.hpp @@ -70,6 +70,8 @@ class os::Bsd { // Real-time clock functions static void clock_init(void); + static void *dlopen_helper(const char *path, int mode); + // Stack repair handling // none present diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 2a63409be1c..6dc76f40d8c 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -95,6 +95,7 @@ # include # include # include +# include # include # include # include @@ -1802,6 +1803,25 @@ void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { } void * os::Linux::dlopen_helper(const char *filename, char *ebuf, int ebuflen) { +#ifndef IA32 + // Save and restore the floating-point environment around dlopen(). + // There are known cases where global library initialization sets + // FPU flags that affect computation accuracy, for example, enabling + // Flush-To-Zero and Denormals-Are-Zero. Do not let those libraries + // break Java arithmetic. Unfortunately, this might affect libraries + // that might depend on these FPU features for performance and/or + // numerical "accuracy", but we need to protect Java semantics first + // and foremost. See JDK-8295159. + + // This workaround is ineffective on IA32 systems because the MXCSR + // register (which controls flush-to-zero mode) is not stored in the + // legacy fenv. + + fenv_t default_fenv; + int rtn = fegetenv(&default_fenv); + assert(rtn == 0, "fegetenv must succeed"); +#endif // IA32 + void* result; JFR_ONLY(NativeLibraryLoadEvent load_event(filename, &result);) result = ::dlopen(filename, RTLD_LAZY); @@ -1820,6 +1840,16 @@ void * os::Linux::dlopen_helper(const char *filename, char *ebuf, int ebuflen) { } else { Events::log_dll_message(nullptr, "Loaded shared library %s", filename); log_info(os)("shared library load of %s was successful", filename); +#ifndef IA32 + // Quickly test to make sure subnormals are correctly handled. + if (! IEEE_subnormal_handling_OK()) { + // We just dlopen()ed a library that mangled the floating-point + // flags. Silently fix things now. + int rtn = fesetenv(&default_fenv); + assert(rtn == 0, "fesetenv must succeed"); + assert(IEEE_subnormal_handling_OK(), "fsetenv didn't work"); + } +#endif // IA32 } return result; } diff --git a/src/hotspot/share/utilities/globalDefinitions.cpp b/src/hotspot/share/utilities/globalDefinitions.cpp index 0c8a28a6111..7d1d68e6476 100644 --- a/src/hotspot/share/utilities/globalDefinitions.cpp +++ b/src/hotspot/share/utilities/globalDefinitions.cpp @@ -409,3 +409,32 @@ STATIC_ASSERT(nth_bit(1|2) == 0x8); STATIC_ASSERT(right_n_bits(3) == 0x7); STATIC_ASSERT(right_n_bits(1|2) == 0x7); + +// Check for Flush-To-Zero mode + +// On some processors faster execution can be achieved by setting a +// mode to return zero for extremely small results, rather than an +// IEEE-754 subnormal number. This mode is not compatible with the +// Java Language Standard. + +// We need the addition of _large_subnormal and _small_subnormal to be +// performed at runtime. _small_subnormal is volatile so that +// expressions involving it cannot be evaluated at compile time. +static const double large_subnormal_double + = jdouble_cast(0x0030000000000000); // 0x1.0p-1020; +static const volatile double small_subnormal_double + = jdouble_cast(0x0000000000000003); // 0x0.0000000000003p-1022; + +// Quickly test to make sure IEEE-754 subnormal numbers are correctly +// handled. +bool IEEE_subnormal_handling_OK() { + // _small_subnormal is the smallest subnormal number that has two + // bits set. _large_subnormal is a number such that, when + // _small_subnormal is added to it, must be rounded according to the + // mode. These two tests detect the rounding mode in use. If + // subnormals are turned off (i.e. subnormals-are-zero) flush-to- + // zero mode is in use. + + return (large_subnormal_double + small_subnormal_double > large_subnormal_double + && -large_subnormal_double - small_subnormal_double < -large_subnormal_double); +} diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 64d12de094c..fe50ddf6de1 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -1335,4 +1335,8 @@ template int primitive_compare(const K& k0, const K& k1) { template std::add_rvalue_reference_t declval() noexcept; +// Quickly test to make sure IEEE-754 subnormal numbers are correctly +// handled. +bool IEEE_subnormal_handling_OK(); + #endif // SHARE_UTILITIES_GLOBALDEFINITIONS_HPP diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 6bdbad4e796..311cd10b4f3 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -75,6 +75,9 @@ compiler/jvmci/TestUncaughtErrorInCompileMethod.java 8309073 generic-all compiler/codecache/CheckLargePages.java 8317831 linux-x64 +compiler/floatingpoint/TestSubnormalFloat.java 8317810 generic-i586 +compiler/floatingpoint/TestSubnormalDouble.java 8317810 generic-i586 + ############################################################################# # :hotspot_gc diff --git a/test/hotspot/jtreg/compiler/floatingpoint/TestSubnormalDouble.java b/test/hotspot/jtreg/compiler/floatingpoint/TestSubnormalDouble.java new file mode 100644 index 00000000000..7c556f1db44 --- /dev/null +++ b/test/hotspot/jtreg/compiler/floatingpoint/TestSubnormalDouble.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023, Red Hat, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8295159 + * @summary DSO created with -ffast-math breaks Java floating-point arithmetic + * @run main/othervm/native compiler.floatingpoint.TestSubnormalDouble + */ + +package compiler.floatingpoint; + +import static java.lang.System.loadLibrary; + +public class TestSubnormalDouble { + static volatile double lastDouble; + + private static void testDoubles() { + lastDouble = 0x1.0p-1074; + for (double x = lastDouble * 2; x <= 0x1.0p1022; x *= 2) { + if (x != x || x <= lastDouble) { + throw new RuntimeException("TEST FAILED: " + x); + } + lastDouble = x; + } + } + + public static void main(String[] args) { + testDoubles(); + System.out.println("Loading libfast-math.so"); + loadLibrary("fast-math"); + testDoubles(); + System.out.println("Test passed."); + } +} diff --git a/test/hotspot/jtreg/compiler/floatingpoint/TestSubnormalFloat.java b/test/hotspot/jtreg/compiler/floatingpoint/TestSubnormalFloat.java new file mode 100644 index 00000000000..f09ee1cf004 --- /dev/null +++ b/test/hotspot/jtreg/compiler/floatingpoint/TestSubnormalFloat.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023, Red Hat, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8295159 + * @summary DSO created with -ffast-math breaks Java floating-point arithmetic + * @run main/othervm/native compiler.floatingpoint.TestSubnormalFloat + */ + +package compiler.floatingpoint; + +import static java.lang.System.loadLibrary; + +public class TestSubnormalFloat { + static volatile float lastFloat; + + private static void testFloats() { + lastFloat = 0x1.0p-149f; + for (float x = lastFloat * 2; x <= 0x1.0p127f; x *= 2) { + if (x != x || x <= lastFloat) { + throw new RuntimeException("TEST FAILED: " + x); + } + lastFloat = x; + } + } + + public static void main(String[] args) { + testFloats(); + System.out.println("Loading libfast-math.so"); + loadLibrary("fast-math"); + testFloats(); + System.out.println("Test passed."); + } +} diff --git a/test/hotspot/jtreg/compiler/floatingpoint/libfast-math.c b/test/hotspot/jtreg/compiler/floatingpoint/libfast-math.c new file mode 100644 index 00000000000..8a65303bc7c --- /dev/null +++ b/test/hotspot/jtreg/compiler/floatingpoint/libfast-math.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, Red Hat, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "jni.h" + +// See GCC bug 55522: +// +// "When used at link-time, [ GCC with -ffast-math ] may include +// libraries or startup files that change the default FPU control word +// or other similar optimizations." +// +// This breaks Java's floating point arithmetic. + +#if defined(__GNUC__) + +// On systems on which GCC bug 55522 has been fixed, this constructor +// serves to reproduce that bug for the purposes of testing HotSpot. +static void __attribute__((constructor)) set_flush_to_zero(void) { + +#if defined(__x86_64__) + +#define MXCSR_DAZ (1 << 6) /* Enable denormals are zero mode */ +#define MXCSR_FTZ (1 << 15) /* Enable flush to zero mode */ + unsigned int mxcsr = __builtin_ia32_stmxcsr (); + mxcsr |= MXCSR_DAZ | MXCSR_FTZ; + __builtin_ia32_ldmxcsr (mxcsr); + +#elif defined(__aarch64__) + +#define _FPU_FPCR_FZ (unsigned long)0x1000000 +#define _FPU_SETCW(fpcr) \ + __asm__ __volatile__ ("msr fpcr, %0" : : "r" (fpcr)); + + /* Flush to zero, round to nearest, IEEE exceptions disabled. */ + _FPU_SETCW (_FPU_FPCR_FZ); + +#endif // CPU arch + +} +#endif // defined(__GNUC__)