1/* $NetBSD: t_fenv.c,v 1.18 2024/05/14 14:55:44 riastradh Exp $ */ 2 3/*- 4 * Copyright (c) 2014 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Martin Husemann. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31#include <sys/cdefs.h> 32__RCSID("$NetBSD: t_fenv.c,v 1.18 2024/05/14 14:55:44 riastradh Exp $"); 33 34#include <atf-c.h> 35 36#include <fenv.h> 37#ifdef __HAVE_FENV 38 39/* XXXGCC gcc lacks #pragma STDC FENV_ACCESS */ 40/* XXXclang clang lacks #pragma STDC FENV_ACCESS on some ports */ 41#if !defined(__GNUC__) 42#pragma STDC FENV_ACCESS ON 43#endif 44 45#include <float.h> 46#include <ieeefp.h> 47#include <stdlib.h> 48 49#if FLT_RADIX != 2 50#error This test assumes binary floating-point arithmetic. 51#endif 52 53#if (__arm__ && !__SOFTFP__) || __aarch64__ 54 /* 55 * Some NEON fpus do not trap on IEEE 754 FP exceptions. 56 * Skip these tests if running on them and compiled for 57 * hard float. 58 */ 59#define FPU_EXC_PREREQ() do \ 60{ \ 61 if (0 == fpsetmask(fpsetmask(FP_X_INV))) \ 62 atf_tc_skip("FPU does not implement traps on FP exceptions"); \ 63} while (0) 64 65 /* 66 * Same as above: some don't allow configuring the rounding mode. 67 */ 68#define FPU_RND_PREREQ() do \ 69{ \ 70 if (0 == fpsetround(fpsetround(FP_RZ))) \ 71 atf_tc_skip("FPU does not implement configurable " \ 72 "rounding modes"); \ 73} while (0) 74#endif 75 76#ifdef __riscv__ 77#define FPU_EXC_PREREQ() \ 78 atf_tc_skip("RISC-V does not support floating-point exception traps") 79#endif 80 81#ifndef FPU_EXC_PREREQ 82#define FPU_EXC_PREREQ() __nothing 83#endif 84#ifndef FPU_RND_PREREQ 85#define FPU_RND_PREREQ() __nothing 86#endif 87 88 89static int 90feround_to_fltrounds(int feround) 91{ 92 93 /* 94 * C99, Sec. 5.2.4.2.2 Characteristics of floating types 95 * <float.h>, p. 24, clause 7 96 */ 97 switch (feround) { 98 case FE_TOWARDZERO: 99 return 0; 100 case FE_TONEAREST: 101 return 1; 102 case FE_UPWARD: 103 return 2; 104 case FE_DOWNWARD: 105 return 3; 106 default: 107 return -1; 108 } 109} 110 111static void 112checkfltrounds(void) 113{ 114 int feround = fegetround(); 115 int expected = feround_to_fltrounds(feround); 116 117 ATF_CHECK_EQ_MSG(FLT_ROUNDS, expected, 118 "FLT_ROUNDS=%d expected=%d fegetround()=%d", 119 FLT_ROUNDS, expected, feround); 120} 121 122static void 123checkrounding(int feround, const char *name) 124{ 125 volatile double ulp1 = DBL_EPSILON; 126 127 /* 128 * XXX These must be volatile to force rounding to double when 129 * the intermediate quantities are evaluated in long double 130 * precision, e.g. on 32-bit x86 with x87 long double. Under 131 * the C standard (C99, C11, C17, &c.), cast and assignment 132 * operators are required to remove all extra range and 133 * precision, i.e., round double to long double. But we build 134 * this code with -std=gnu99, which diverges from this 135 * requirement -- unless you add a volatile qualifier. 136 */ 137 volatile double y1 = -1 + ulp1/4; 138 volatile double y2 = 1 + 3*(ulp1/2); 139 140 double z1, z2; 141 142 switch (feround) { 143 case FE_TONEAREST: 144 z1 = -1; 145 z2 = 1 + 2*ulp1; 146 break; 147 case FE_TOWARDZERO: 148 z1 = -1 + ulp1/2; 149 z2 = 1 + ulp1; 150 break; 151 case FE_UPWARD: 152 z1 = -1 + ulp1/2; 153 z2 = 1 + 2*ulp1; 154 break; 155 case FE_DOWNWARD: 156 z1 = -1; 157 z2 = 1 + ulp1; 158 break; 159 default: 160 atf_tc_fail("unknown rounding mode %d (%s)", feround, name); 161 } 162 163 ATF_CHECK_EQ_MSG(y1, z1, "%s[-1 + ulp(1)/4] expected=%a actual=%a", 164 name, y1, z1); 165 ATF_CHECK_EQ_MSG(y2, z2, "%s[1 + 3*(ulp(1)/2)] expected=%a actual=%a", 166 name, y2, z2); 167} 168 169ATF_TC(fegetround); 170 171ATF_TC_HEAD(fegetround, tc) 172{ 173 atf_tc_set_md_var(tc, "descr", 174 "verify the fegetround() function agrees with the legacy " 175 "fpsetround"); 176} 177 178ATF_TC_BODY(fegetround, tc) 179{ 180 FPU_RND_PREREQ(); 181 182 checkrounding(FE_TONEAREST, "FE_TONEAREST"); 183 184 fpsetround(FP_RZ); 185 ATF_CHECK_EQ_MSG(fegetround(), FE_TOWARDZERO, 186 "fegetround()=%d FE_TOWARDZERO=%d", 187 fegetround(), FE_TOWARDZERO); 188 checkfltrounds(); 189 checkrounding(FE_TOWARDZERO, "FE_TOWARDZERO"); 190 191 fpsetround(FP_RM); 192 ATF_CHECK_EQ_MSG(fegetround(), FE_DOWNWARD, 193 "fegetround()=%d FE_DOWNWARD=%d", 194 fegetround(), FE_DOWNWARD); 195 checkfltrounds(); 196 checkrounding(FE_DOWNWARD, "FE_DOWNWARD"); 197 198 fpsetround(FP_RN); 199 ATF_CHECK_EQ_MSG(fegetround(), FE_TONEAREST, 200 "fegetround()=%d FE_TONEAREST=%d", 201 fegetround(), FE_TONEAREST); 202 checkfltrounds(); 203 checkrounding(FE_TONEAREST, "FE_TONEAREST"); 204 205 fpsetround(FP_RP); 206 ATF_CHECK_EQ_MSG(fegetround(), FE_UPWARD, 207 "fegetround()=%d FE_UPWARD=%d", 208 fegetround(), FE_UPWARD); 209 checkfltrounds(); 210 checkrounding(FE_UPWARD, "FE_UPWARD"); 211} 212 213ATF_TC(fesetround); 214 215ATF_TC_HEAD(fesetround, tc) 216{ 217 atf_tc_set_md_var(tc, "descr", 218 "verify the fesetround() function agrees with the legacy " 219 "fpgetround"); 220} 221 222ATF_TC_BODY(fesetround, tc) 223{ 224 FPU_RND_PREREQ(); 225 226 checkrounding(FE_TONEAREST, "FE_TONEAREST"); 227 228 fesetround(FE_TOWARDZERO); 229 ATF_CHECK_EQ_MSG(fpgetround(), FP_RZ, 230 "fpgetround()=%d FP_RZ=%d", 231 (int)fpgetround(), (int)FP_RZ); 232 checkfltrounds(); 233 checkrounding(FE_TOWARDZERO, "FE_TOWARDZERO"); 234 235 fesetround(FE_DOWNWARD); 236 ATF_CHECK_EQ_MSG(fpgetround(), FP_RM, 237 "fpgetround()=%d FP_RM=%d", 238 (int)fpgetround(), (int)FP_RM); 239 checkfltrounds(); 240 checkrounding(FE_DOWNWARD, "FE_DOWNWARD"); 241 242 fesetround(FE_TONEAREST); 243 ATF_CHECK_EQ_MSG(fpgetround(), FP_RN, 244 "fpgetround()=%d FP_RN=%d", 245 (int)fpgetround(), (int)FP_RN); 246 checkfltrounds(); 247 checkrounding(FE_TONEAREST, "FE_TONEAREST"); 248 249 fesetround(FE_UPWARD); 250 ATF_CHECK_EQ_MSG(fpgetround(), FP_RP, 251 "fpgetround()=%d FP_RP=%d", 252 (int)fpgetround(), (int)FP_RP); 253 checkfltrounds(); 254 checkrounding(FE_UPWARD, "FE_UPWARD"); 255} 256 257ATF_TC(fegetexcept); 258 259ATF_TC_HEAD(fegetexcept, tc) 260{ 261 atf_tc_set_md_var(tc, "descr", 262 "verify the fegetexcept() function agrees with the legacy " 263 "fpsetmask()"); 264} 265 266ATF_TC_BODY(fegetexcept, tc) 267{ 268 FPU_EXC_PREREQ(); 269 270 fpsetmask(0); 271 ATF_CHECK_EQ_MSG(fegetexcept(), 0, 272 "fegetexcept()=%d", 273 fegetexcept()); 274 275 fpsetmask(FP_X_INV|FP_X_DZ|FP_X_OFL|FP_X_UFL|FP_X_IMP); 276 ATF_CHECK(fegetexcept() == (FE_INVALID|FE_DIVBYZERO|FE_OVERFLOW 277 |FE_UNDERFLOW|FE_INEXACT)); 278 279 fpsetmask(FP_X_INV); 280 ATF_CHECK_EQ_MSG(fegetexcept(), FE_INVALID, 281 "fegetexcept()=%d FE_INVALID=%d", 282 fegetexcept(), FE_INVALID); 283 284 fpsetmask(FP_X_DZ); 285 ATF_CHECK_EQ_MSG(fegetexcept(), FE_DIVBYZERO, 286 "fegetexcept()=%d FE_DIVBYZERO=%d", 287 fegetexcept(), FE_DIVBYZERO); 288 289 fpsetmask(FP_X_OFL); 290 ATF_CHECK_EQ_MSG(fegetexcept(), FE_OVERFLOW, 291 "fegetexcept()=%d FE_OVERFLOW=%d", 292 fegetexcept(), FE_OVERFLOW); 293 294 fpsetmask(FP_X_UFL); 295 ATF_CHECK_EQ_MSG(fegetexcept(), FE_UNDERFLOW, 296 "fegetexcept()=%d FE_UNDERFLOW=%d", 297 fegetexcept(), FE_UNDERFLOW); 298 299 fpsetmask(FP_X_IMP); 300 ATF_CHECK_EQ_MSG(fegetexcept(), FE_INEXACT, 301 "fegetexcept()=%d FE_INEXACT=%d", 302 fegetexcept(), FE_INEXACT); 303} 304 305ATF_TC(feenableexcept); 306 307ATF_TC_HEAD(feenableexcept, tc) 308{ 309 atf_tc_set_md_var(tc, "descr", 310 "verify the feenableexcept() function agrees with the legacy " 311 "fpgetmask()"); 312} 313 314ATF_TC_BODY(feenableexcept, tc) 315{ 316 FPU_EXC_PREREQ(); 317 318 fedisableexcept(FE_ALL_EXCEPT); 319 ATF_CHECK_EQ_MSG(fpgetmask(), 0, 320 "fpgetmask()=%d", 321 (int)fpgetmask()); 322 323 feenableexcept(FE_UNDERFLOW); 324 ATF_CHECK_EQ_MSG(fpgetmask(), FP_X_UFL, 325 "fpgetmask()=%d FP_X_UFL=%d", 326 (int)fpgetmask(), (int)FP_X_UFL); 327 328 fedisableexcept(FE_ALL_EXCEPT); 329 feenableexcept(FE_OVERFLOW); 330 ATF_CHECK_EQ_MSG(fpgetmask(), FP_X_OFL, 331 "fpgetmask()=%d FP_X_OFL=%d", 332 (int)fpgetmask(), (int)FP_X_OFL); 333 334 fedisableexcept(FE_ALL_EXCEPT); 335 feenableexcept(FE_DIVBYZERO); 336 ATF_CHECK_EQ_MSG(fpgetmask(), FP_X_DZ, 337 "fpgetmask()=%d FP_X_DZ=%d", 338 (int)fpgetmask(), (int)FP_X_DZ); 339 340 fedisableexcept(FE_ALL_EXCEPT); 341 feenableexcept(FE_INEXACT); 342 ATF_CHECK_EQ_MSG(fpgetmask(), FP_X_IMP, 343 "fpgetmask()=%d FP_X_IMP=%d", 344 (int)fpgetmask(), (int)FP_X_IMP); 345 346 fedisableexcept(FE_ALL_EXCEPT); 347 feenableexcept(FE_INVALID); 348 ATF_CHECK_EQ_MSG(fpgetmask(), FP_X_INV, 349 "fpgetmask()=%d FP_X_INV=%d", 350 (int)fpgetmask(), (int)FP_X_INV); 351} 352 353/* 354 * Temporary workaround for PR 58253: powerpc has more fenv exception 355 * bits than it can handle. 356 */ 357#if defined __powerpc__ 358#define FE_TRAP_EXCEPT \ 359 (FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW) 360#else 361#define FE_TRAP_EXCEPT FE_ALL_EXCEPT 362#endif 363 364ATF_TC(fetestexcept_trap); 365ATF_TC_HEAD(fetestexcept_trap, tc) 366{ 367 atf_tc_set_md_var(tc, "descr", 368 "Verify fetestexcept doesn't affect the trapped excpetions"); 369} 370ATF_TC_BODY(fetestexcept_trap, tc) 371{ 372 int except; 373 374 FPU_EXC_PREREQ(); 375 376 fedisableexcept(FE_TRAP_EXCEPT); 377 ATF_CHECK_EQ_MSG((except = fegetexcept()), 0, 378 "fegetexcept()=0x%x", except); 379 380 (void)fetestexcept(FE_TRAP_EXCEPT); 381 ATF_CHECK_EQ_MSG((except = fegetexcept()), 0, 382 "fegetexcept()=0x%x", except); 383 384 feenableexcept(FE_TRAP_EXCEPT); 385 ATF_CHECK_EQ_MSG((except = fegetexcept()), FE_TRAP_EXCEPT, 386 "fegetexcept()=0x%x FE_TRAP_EXCEPT=0x%x", except, FE_TRAP_EXCEPT); 387 388 (void)fetestexcept(FE_TRAP_EXCEPT); 389 ATF_CHECK_EQ_MSG((except = fegetexcept()), FE_TRAP_EXCEPT, 390 "fegetexcept()=0x%x FE_ALL_EXCEPT=0x%x", except, FE_TRAP_EXCEPT); 391} 392 393ATF_TP_ADD_TCS(tp) 394{ 395 ATF_TP_ADD_TC(tp, fegetround); 396 ATF_TP_ADD_TC(tp, fesetround); 397 ATF_TP_ADD_TC(tp, fegetexcept); 398 ATF_TP_ADD_TC(tp, feenableexcept); 399 ATF_TP_ADD_TC(tp, fetestexcept_trap); 400 401 return atf_no_error(); 402} 403 404#else /* no fenv.h support */ 405 406ATF_TC(t_nofenv); 407 408ATF_TC_HEAD(t_nofenv, tc) 409{ 410 atf_tc_set_md_var(tc, "descr", 411 "dummy test case - no fenv.h support"); 412} 413 414 415ATF_TC_BODY(t_nofenv, tc) 416{ 417 atf_tc_skip("no fenv.h support on this architecture"); 418} 419 420ATF_TP_ADD_TCS(tp) 421{ 422 ATF_TP_ADD_TC(tp, t_nofenv); 423 return atf_no_error(); 424} 425 426#endif 427