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