Serenity Operating System
1/*
2 * Copyright (c) 2021, Mițca Dumitru <dumitru0mitca@gmail.com>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Types.h>
8#include <fenv.h>
9
10// This is the size of the floating point environment image in protected mode
11static_assert(sizeof(__x87_floating_point_environment) == 28);
12
13#ifndef AK_ARCH_AARCH64
14static u16 read_status_register()
15{
16 u16 status_register;
17 asm volatile("fnstsw %0"
18 : "=m"(status_register));
19 return status_register;
20}
21
22static u16 read_control_word()
23{
24 u16 control_word;
25 asm volatile("fnstcw %0"
26 : "=m"(control_word));
27 return control_word;
28}
29
30static void set_control_word(u16 new_control_word)
31{
32 asm volatile("fldcw %0" ::"m"(new_control_word));
33}
34
35static u32 read_mxcsr()
36{
37 u32 mxcsr;
38 asm volatile("stmxcsr %0"
39 : "=m"(mxcsr));
40 return mxcsr;
41}
42
43static void set_mxcsr(u32 new_mxcsr)
44{
45 asm volatile("ldmxcsr %0" ::"m"(new_mxcsr));
46}
47
48static constexpr u32 default_mxcsr_value = 0x1f80;
49#endif
50
51extern "C" {
52
53int fegetenv(fenv_t* env)
54{
55 if (!env)
56 return 1;
57
58#ifdef AK_ARCH_AARCH64
59 (void)env;
60 TODO_AARCH64();
61#else
62 asm volatile("fnstenv %0"
63 : "=m"(env->__x87_fpu_env)::"memory");
64
65 env->__mxcsr = read_mxcsr();
66#endif
67
68 return 0;
69}
70
71int fesetenv(fenv_t const* env)
72{
73 if (!env)
74 return 1;
75
76#ifdef AK_ARCH_AARCH64
77 (void)env;
78 TODO_AARCH64();
79#else
80 if (env == FE_DFL_ENV) {
81 asm volatile("finit");
82 set_mxcsr(default_mxcsr_value);
83 return 0;
84 }
85
86 asm volatile("fldenv %0" ::"m"(env->__x87_fpu_env)
87 : "memory");
88
89 set_mxcsr(env->__mxcsr);
90#endif
91
92 return 0;
93}
94
95int feholdexcept(fenv_t* env)
96{
97 fegetenv(env);
98
99 fenv_t current_env;
100 fegetenv(¤t_env);
101
102#ifdef AK_ARCH_AARCH64
103 (void)env;
104 TODO_AARCH64();
105#else
106 current_env.__x87_fpu_env.__status_word &= ~FE_ALL_EXCEPT;
107 current_env.__x87_fpu_env.__status_word &= ~(1 << 7); // Clear the "Exception Status Summary" bit
108 current_env.__x87_fpu_env.__control_word &= FE_ALL_EXCEPT; // Masking these bits stops the corresponding exceptions from being generated according to the Intel Programmer's Manual
109#endif
110
111 fesetenv(¤t_env);
112
113 return 0;
114}
115
116int feupdateenv(fenv_t const* env)
117{
118 auto currently_raised_exceptions = fetestexcept(FE_ALL_EXCEPT);
119
120 fesetenv(env);
121 feraiseexcept(currently_raised_exceptions);
122
123 return 0;
124}
125
126int fegetexceptflag(fexcept_t* except, int exceptions)
127{
128 if (!except)
129 return 1;
130 *except = (uint16_t)fetestexcept(exceptions);
131 return 0;
132}
133int fesetexceptflag(fexcept_t const* except, int exceptions)
134{
135 if (!except)
136 return 1;
137
138 fenv_t current_env;
139 fegetenv(¤t_env);
140
141 exceptions &= FE_ALL_EXCEPT;
142#ifdef AK_ARCH_AARCH64
143 (void)exceptions;
144 (void)except;
145 TODO_AARCH64();
146#else
147 current_env.__x87_fpu_env.__status_word &= exceptions;
148 current_env.__x87_fpu_env.__status_word &= ~(1 << 7); // Make sure exceptions don't get raised
149#endif
150
151 fesetenv(¤t_env);
152 return 0;
153}
154
155int fegetround()
156{
157#ifdef AK_ARCH_AARCH64
158 TODO_AARCH64();
159#else
160 // There's no way to signal whether the SSE rounding mode and x87 ones are different, so we assume they're the same
161 return (read_status_register() >> 10) & 3;
162#endif
163}
164
165int fesetround(int rounding_mode)
166{
167 if (rounding_mode < FE_TONEAREST || rounding_mode > FE_TOWARDZERO)
168 return 1;
169
170#ifdef AK_ARCH_AARCH64
171 TODO_AARCH64();
172#else
173 auto control_word = read_control_word();
174
175 control_word &= ~(3 << 10);
176 control_word |= rounding_mode << 10;
177
178 set_control_word(control_word);
179
180 auto mxcsr = read_mxcsr();
181
182 mxcsr &= ~(3 << 13);
183 mxcsr |= rounding_mode << 13;
184
185 set_mxcsr(mxcsr);
186
187#endif
188
189 return 0;
190}
191
192int feclearexcept(int exceptions)
193{
194 exceptions &= FE_ALL_EXCEPT;
195
196 fenv_t current_env;
197 fegetenv(¤t_env);
198
199#ifdef AK_ARCH_AARCH64
200 (void)exceptions;
201 TODO_AARCH64();
202#else
203 current_env.__x87_fpu_env.__status_word &= ~exceptions;
204 current_env.__x87_fpu_env.__status_word &= ~(1 << 7); // Clear the "Exception Status Summary" bit
205#endif
206
207 fesetenv(¤t_env);
208 return 0;
209}
210
211int fetestexcept(int exceptions)
212{
213#ifdef AK_ARCH_AARCH64
214 (void)exceptions;
215 TODO_AARCH64();
216#else
217 u16 status_register = read_status_register() & FE_ALL_EXCEPT;
218 exceptions &= FE_ALL_EXCEPT;
219
220 return status_register & exceptions;
221#endif
222}
223
224int feraiseexcept(int exceptions)
225{
226 fenv_t env;
227 fegetenv(&env);
228
229 exceptions &= FE_ALL_EXCEPT;
230
231#ifdef AK_ARCH_AARCH64
232 (void)exceptions;
233 TODO_AARCH64();
234#else
235 // While the order in which the exceptions is raised is unspecified, FE_OVERFLOW and FE_UNDERFLOW must be raised before FE_INEXACT, so handle that case in this branch
236 if (exceptions & FE_INEXACT) {
237 env.__x87_fpu_env.__status_word &= ((u16)exceptions & ~FE_INEXACT);
238 fesetenv(&env);
239 asm volatile("fwait"); // "raise" the exception by performing a floating point operation
240
241 fegetenv(&env);
242 env.__x87_fpu_env.__status_word &= FE_INEXACT;
243 fesetenv(&env);
244 asm volatile("fwait");
245
246 return 0;
247 }
248
249 env.__x87_fpu_env.__status_word &= exceptions;
250 fesetenv(&env);
251 asm volatile("fwait");
252#endif
253
254 return 0;
255}
256}