C++学习------cfenv头文件的作用与源码分析02

419 阅读11分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

fenv.h 的函数定义

可以明显地看出,函数定义分为四类:

  • 管理异常 flag 位的
  • 管理浮点数舍入方向的
  • 管理整体环境变量的
  • 管理异常整体控制的
44  // fenv was always available on x86.
45  #if __ANDROID_API__ >= 21 || defined(__i386__)
46  int feclearexcept(int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
47  int fegetexceptflag(fexcept_t* __flag_ptr, int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
48  int feraiseexcept(int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
49  int fesetexceptflag(const fexcept_t* __flag_ptr, int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
50  int fetestexcept(int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
51  
52  int fegetround(void) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
53  int fesetround(int __rounding_mode) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
54  
55  int fegetenv(fenv_t* __env) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
56  int feholdexcept(fenv_t* __env) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
57  int fesetenv(const fenv_t* __env) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
58  int feupdateenv(const fenv_t* __env) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
59  
60  int feenableexcept(int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
61  int fedisableexcept(int __exceptions) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
62  int fegetexcept(void) __INTRODUCED_IN_ARM(21) __INTRODUCED_IN_X86(9);
63  #else
64  /* Defined as inlines for pre-21 ARM. */
65  #endif

查看其对应的实现.c 文件

只发现了 amd64,arm64,i387 的实现,具体该选择哪一个呢?我们来看看具体的编译情况。

www.aospxref.com/android-12.…\

22 cc_library {
23     name: "libm",
24     defaults: ["linux_bionic_supported"],
25     ramdisk_available: true,
26     vendor_ramdisk_available: true,
27     recovery_available: true,
28     static_ndk_lib: true,
29 
30     whole_static_libs: ["libarm-optimized-routines-math"],
31 
...
285     // arch-specific settings
286     arch: {
287         arm: {
288             srcs: [
289                 "arm/fenv.c",
290             ],
...
314         arm64: {
315             srcs: [
316                 "arm64/fenv.c",
317                 "arm64/lrint.S",
318                 "arm64/sqrt.S",
319             ],
347         x86: {
348             srcs: [
349                 "i387/fenv.c",
...
416         x86_64: {
417             srcs: [
418                 "amd64/fenv.c",

可以知道,x86_64 使用的是 amd64/fenv.c 的实现,接下我我们就分析该文件中的具体实现。

www.aospxref.com/android-12.…\

管理异常 flag 位

feclearexcept---清空异常检测

作用是清空输入指定的异常,不进行监控。

77  /*
78   * The feclearexcept() function clears the supported floating-point exceptions
79   * represented by `excepts'.
80   */
81  int
82  feclearexcept(int excepts)
83  {
84    fenv_t fenv;
85    unsigned int mxcsr;
86  
87    excepts &= FE_ALL_EXCEPT;
88  
89    /* Store the current x87 floating-point environment */
90    __asm__ __volatile__ ("fnstenv %0" : "=m" (fenv));
91  
92    /* Clear the requested floating-point exceptions */
93    fenv.__x87.__status &= ~excepts;
94  
95    /* Load the x87 floating-point environent */
96    __asm__ __volatile__ ("fldenv %0" : : "m" (fenv));
97  
98    /* Same for SSE environment */
99    __asm__ __volatile__ ("stmxcsr %0" : "=m" (mxcsr));
100    mxcsr &= ~excepts;
101    __asm__ __volatile__ ("ldmxcsr %0" : : "m" (mxcsr));
102  
103    return (0);
104  }

通过代码,可以看到,首先将输入值与 FE_ALL_EXCEPT 做按位与,截断操作数据,保证后续操作是针对有效的异常位;通过__asm__汇编嵌入语句,fnstenv 获取当前的环境变量,然后使用取反再按位与的操作,将想要清除的异常位置为 0,保留其它位,变量为 fenv.__x87.__status;然后 fldenv 加载修改后的环境变量;相同的操作对 SSE(Streaming SIMD Extensions) 环境变量进行处理,主要是对 MXCSR 寄存器进行操作,该寄存器也是管理浮点数运算相关的。

feraiseexcept---更新异常检测

使用输入值更新异常检测环境信息,主要的逻辑还是在 fesetexceptflag 函数中

131  /*
132   * The feraiseexcept() function raises the supported floating-point exceptions
133   * represented by the argument `excepts'.
134   *
135   * The standard explicitly allows us to execute an instruction that has the
136   * exception as a side effect, but we choose to manipulate the status register
137   * directly.
138   *
139   * The validation of input is being deferred to fesetexceptflag().
140   */
141  int
142  feraiseexcept(int excepts)
143  {
144    excepts &= FE_ALL_EXCEPT;
145  
146    fesetexceptflag((fexcept_t *)&excepts, excepts);
147    __asm__ __volatile__ ("fwait");
148  
149    return (0);
150  }

fesetexceptflag---设置异常检测 tag

相关的汇编操作与 feclearexcept 类似,这次 set 操作的两个输入值其实是同一个值,一个只读,一个可改写,对位运算进行两次校验,保证修改正确。

152  /*
153   * This function sets the floating-point status flags indicated by the argument
154   * `excepts' to the states stored in the object pointed to by `flagp'. It does
155   * NOT raise any floating-point exceptions, but only sets the state of the flags.
156   */
157  int
158  fesetexceptflag(const fexcept_t *flagp, int excepts)
159  {
160    fenv_t fenv;
161    unsigned int mxcsr;
162  
163    excepts &= FE_ALL_EXCEPT;
164  
165    /* Store the current x87 floating-point environment */
166    __asm__ __volatile__ ("fnstenv %0" : "=m" (fenv));
167  
168    /* Set the requested status flags */
169    fenv.__x87.__status &= ~excepts;
170    fenv.__x87.__status |= *flagp & excepts;
171  
172    /* Load the x87 floating-point environent */
173    __asm__ __volatile__ ("fldenv %0" : : "m" (fenv));
174  
175    /* Same for SSE environment */
176    __asm__ __volatile__ ("stmxcsr %0" : "=m" (mxcsr));
177    mxcsr &= ~excepts;
178    mxcsr |= *flagp & excepts;
179    __asm__ __volatile__ ("ldmxcsr %0" : : "m" (mxcsr));
180  
181    return (0);
182  }

fegetexceptflag---读取异常检测 tag

获取对应的异常信息,注意,这里从两个地方获取之后做了与操作,然后返回值通过 flagp 返回

106  /*
107   * The fegetexceptflag() function stores an implementation-defined
108   * representation of the states of the floating-point status flags indicated by
109   * the argument excepts in the object pointed to by the argument flagp.
110   */
111  int
112  fegetexceptflag(fexcept_t *flagp, int excepts)
113  {
114    unsigned short status;
115    unsigned int mxcsr;
116  
117    excepts &= FE_ALL_EXCEPT;
118  
119    /* Store the current x87 status register */
120    __asm__ __volatile__ ("fnstsw %0" : "=am" (status));
121  
122    /* Store the MXCSR register */
123    __asm__ __volatile__ ("stmxcsr %0" : "=m" (mxcsr));
124  
125    /* Store the results in flagp */
126    *flagp = (status | mxcsr) & excepts;
127  
128    return (0);
129  }

fetestexcept---检测异常标志位

测试对应的标志位,原理与 get 操作基本一致,但是返回值是对应的 flag 信息

184  /*
185   * The fetestexcept() function determines which of a specified subset of the
186   * floating-point exception flags are currently set. The `excepts' argument
187   * specifies the floating-point status flags to be queried.
188   */
189  int
190  fetestexcept(int excepts)
191  {
192    unsigned short status;
193    unsigned int mxcsr;
194  
195    excepts &= FE_ALL_EXCEPT;
196  
197    /* Store the current x87 status register */
198    __asm__ __volatile__ ("fnstsw %0" : "=am" (status));
199  
200    /* Store the MXCSR register state */
201    __asm__ __volatile__ ("stmxcsr %0" : "=m" (mxcsr));
202  
203    return ((status | mxcsr) & excepts);
204  }

管理浮点数舍入方向

fegetround---读取舍入信息

通过 fnstcw 指令获取返回值 control,并与 X87_ROUND_MASK 做按位与之后返回,也是为了保证是四个有效值中的一个。

46  #define X87_ROUND_MASK  (FE_TONEAREST | FE_DOWNWARD | FE_UPWARD | FE_TOWARDZERO)
...
206  /*
207   * The fegetround() function gets the current rounding direction.
208   */
209  int
210  fegetround(void)
211  {
212    unsigned short control;
213  
214    /*
215     * We assume that the x87 and the SSE unit agree on the
216     * rounding mode.  Reading the control word on the x87 turns
217     * out to be about 5 times faster than reading it on the SSE
218     * unit on an Opteron 244.
219     */
220    __asm__ __volatile__ ("fnstcw %0" : "=m" (control));
221  
222    return (control & X87_ROUND_MASK);
223  }

fesetround---设置舍入信息

fesetround 的操作略微复杂:先验证输入 round 值得有效性,通过位运算处理,如果还不为 0,说明输入参数是非法的,不为四个有效值的组合,直接返回-1 表示 error;通过 fnstcw/fldcw 读写寄存器,将有效信息写入寄存器。注意这里 MXCSR 寄存器是 32 位位宽,它的具体信息如下:

  • SIMD 浮点异常的标志位与掩码位
  • SIMD 浮点操作的舍入控制域
  • 下溢清零标志位(flush-to-zero),控制当 SIMD 浮点操作出现下溢时的结果
  • 非规格化数据作零标志位(denormals-are-zero),控制当 SIMD 浮点操作处理遇到非规格化源操作数时的行为

所以进行舍入控制时,原本 FE_TONEAREST 0x000,FE_DOWNWARD 0x400,FE_UPWARD 0x800,FE_TOWARDZERO 0xc00 进行移位,对应上面的 0x000,0x2000,0x4000,0x6000,分别是

  • flush-to-zero 为 0,round control 为 0:默认最近舍入
  • flush-to-zero 为 0,round control 为 2:向下舍入
  • flush-to-zero 为 1,round control 为 0:向上舍入
  • flush-to-zero 为 1,round control 为 2:向下舍入,且绝对值最近的
46  #define X87_ROUND_MASK  (FE_TONEAREST | FE_DOWNWARD | FE_UPWARD | FE_TOWARDZERO)
47  #define SSE_ROUND_SHIFT 3
225  /*
226   * The fesetround() function establishes the rounding direction represented by
227   * its argument `round'. If the argument is not equal to the value of a rounding
228   * direction macro, the rounding direction is not changed.
229   */
230  int
231  fesetround(int round)
232  {
233    unsigned short control;
234    unsigned int mxcsr;
235  
236    /* Check whether requested rounding direction is supported */
237    if (round & ~X87_ROUND_MASK)
238      return (-1);
239  
240    /* Store the current x87 control word register */
241    __asm__ __volatile__ ("fnstcw %0" : "=m" (control));
242  
243    /* Set the rounding direction */
244    control &= ~X87_ROUND_MASK;
245    control |= round;
246  
247    /* Load the x87 control word register */
248    __asm__ __volatile__ ("fldcw %0" : : "m" (control));
249  
250    /* Same for the SSE environment */
251    __asm__ __volatile__ ("stmxcsr %0" : "=m" (mxcsr));
252    mxcsr &= ~(X87_ROUND_MASK << SSE_ROUND_SHIFT);
253    mxcsr |= round << SSE_ROUND_SHIFT;
254    __asm__ __volatile__ ("ldmxcsr %0" : : "m" (mxcsr));
255  
256    return (0);
257  }

管理整体环境变量

fegetenv---获取环境变量

通过对应的汇编命令返回对应的数据结构,如 x87 整体的数据,MXCSR 寄存器值,最后使用 fldcw 再更新__control 的值(控制舍入的寄存器位)

259  /*
260   * The fegetenv() function attempts to store the current floating-point
261   * environment in the object pointed to by envp.
262   */
263  int
264  fegetenv(fenv_t *envp)
265  {
266    /* Store the current x87 floating-point environment */
267    __asm__ __volatile__ ("fnstenv %0" : "=m" (*envp));
268  
269    /* Store the MXCSR register state */
270    __asm__ __volatile__ ("stmxcsr %0" : "=m" (envp->__mxcsr));
271  
272    /*
273     * When an FNSTENV instruction is executed, all pending exceptions are
274     * essentially lost (either the x87 FPU status register is cleared or
275     * all exceptions are masked).
276     *
277     * 8.6 X87 FPU EXCEPTION SYNCHRONIZATION -
278     * Intel(R) 64 and IA-32 Architectures Softare Developer's Manual - Vol1
279     */
280    __asm__ __volatile__ ("fldcw %0" : : "m" (envp->__x87.__control));
281  
282    return (0);
283  }

feholdexcept---保存异常然后 Reset 打开所有异常

该函数会将当前的状态保存到入参中,然后将所有的异常检测都打开,后面可以通过前面保存的状态重新恢复之前的状态,类似一个快照,在代码中的某些部分不能够屏蔽异常,所以需要先保存快照,然后等这段代码过之后再重新屏蔽异常。

285  /*
286   * The feholdexcept() function saves the current floating-point environment
287   * in the object pointed to by envp, clears the floating-point status flags, and
288   * then installs a non-stop (continue on floating-point exceptions) mode, if
289   * available, for all floating-point exceptions.
290   */
291  int
292  feholdexcept(fenv_t *envp)
293  {
294    unsigned int mxcsr;
295  
296    /* Store the current x87 floating-point environment */
297    __asm__ __volatile__ ("fnstenv %0" : "=m" (*envp));
298  
299    /* Clear all exception flags in FPU */
300    __asm__ __volatile__ ("fnclex");
301  
302    /* Store the MXCSR register state */
303    __asm__ __volatile__ ("stmxcsr %0" : "=m" (envp->__mxcsr));
304  
305    /* Clear exception flags in MXCSR */
306    mxcsr = envp->__mxcsr;
307    mxcsr &= ~FE_ALL_EXCEPT;
308  
309    /* Mask all exceptions */
310    mxcsr |= FE_ALL_EXCEPT << SSE_MASK_SHIFT;
311  
312    /* Store the MXCSR register */
313    __asm__ __volatile__ ("ldmxcsr %0" : : "m" (mxcsr));
314  
315    return (0);
316  }

fesetenv--设置环境变量

使用 fldenv、ldmxcsr 命令

318  /*
319   * The fesetenv() function attempts to establish the floating-point environment
320   * represented by the object pointed to by envp. The argument `envp' points
321   * to an object set by a call to fegetenv() or feholdexcept(), or equal a
322   * floating-point environment macro. The fesetenv() function does not raise
323   * floating-point exceptions, but only installs the state of the floating-point
324   * status flags represented through its argument.
325   */
326  int
327  fesetenv(const fenv_t *envp)
328  {
329    /* Load the x87 floating-point environent */
330    __asm__ __volatile__ ("fldenv %0" : : "m" (*envp));
331  
332    /* Store the MXCSR register */
333    __asm__ __volatile__ ("ldmxcsr %0" : : "m" (envp->__mxcsr));
334  
335    return (0);
336  }

feupdateenv---更新环境变量

实际上是调用前面 fesetenv 和 feraiseexcept 的组合完成。

338  /*
339   * The feupdateenv() function saves the currently raised floating-point
340   * exceptions in its automatic storage, installs the floating-point environment
341   * represented by the object pointed to by `envp', and then raises the saved
342   * floating-point exceptions. The argument `envp' shall point to an object set
343   * by a call to feholdexcept() or fegetenv(), or equal a floating-point
344   * environment macro.
345   */
346  int
347  feupdateenv(const fenv_t *envp)
348  {
349    unsigned short status;
350    unsigned int mxcsr;
351  
352    /* Store the x87 status register */
353    __asm__ __volatile__ ("fnstsw %0" : "=am" (status));
354  
355    /* Store the MXCSR register */
356    __asm__ __volatile__ ("stmxcsr %0" : "=m" (mxcsr));
357  
358    /* Install new floating-point environment */
359    fesetenv(envp);
360  
361    /* Raise any previously accumulated exceptions */
362    feraiseexcept(status | mxcsr);
363  
364    return (0);
365  }

管理异常整体控制

feenableexcept---使能新的异常 mask,返回旧的异常 mask

首先截断输入 mask,保证其有效性,在设置处理上也比较好理解,取反之后再与运算,就是只 enablemask 指定的异常检测,MXCSR 寄存器的操作需要进行移位,左移 7 位正好对应到 Invalid Operation Mask 及之后的位。

这里比较特殊的是还将改变前的 control 和 mxcsr 的信息做了组合,然后取反,即返回之前 enable 的异常 mask.

40  #define SSE_MASK_SHIFT 7
367  /*
368   * The following functions are extentions to the standard
369   */
370  int
371  feenableexcept(int mask)
372  {
373    unsigned int mxcsr, omask;
374    unsigned short control;
375  
376    mask &= FE_ALL_EXCEPT;
377  
378    __asm__ __volatile__ ("fnstcw %0" : "=m" (control));
379    __asm__ __volatile__ ("stmxcsr %0" : "=m" (mxcsr));
380  
381    omask = ~(control | (mxcsr >> SSE_MASK_SHIFT)) & FE_ALL_EXCEPT;
382    control &= ~mask;
383    __asm__ __volatile__ ("fldcw %0" : : "m" (control));
384  
385    mxcsr &= ~(mask << SSE_MASK_SHIFT);
386    __asm__ __volatile__ ("ldmxcsr %0" : : "m" (mxcsr));
387  
388    return (omask);
389  }

fedisableexcept---不使能新的异常 mask,返回旧的异常 mask

操作与 feenableexcept 类似,只是在设置之前直接做或运算

391  int
392  fedisableexcept(int mask)
393  {
394    unsigned int mxcsr, omask;
395    unsigned short control;
396  
397    mask &= FE_ALL_EXCEPT;
398  
399    __asm__ __volatile__ ("fnstcw %0" : "=m" (control));
400    __asm__ __volatile__ ("stmxcsr %0" : "=m" (mxcsr));
401  
402    omask = ~(control | (mxcsr >> SSE_MASK_SHIFT)) & FE_ALL_EXCEPT;
403    control |= mask;
404    __asm__ __volatile__ ("fldcw %0" : : "m" (control));
405  
406    mxcsr |= mask << SSE_MASK_SHIFT;
407    __asm__ __volatile__ ("ldmxcsr %0" : : "m" (mxcsr));
408  
409    return (omask);
410  }

fegetexcept---返回异常 mask

返回取反之后的 mask 与 FE_ALL_EXCEPT 求与,即当前有哪些异常使能了。

注意:这里假设通过 fnstcw 获取的信息与 MXCSR 寄存器的值一致,所以就没有再获取 MXCSR 寄存器的信息进行判断。

412  int
413  fegetexcept(void)
414  {
415    unsigned short control;
416  
417    /*
418     * We assume that the masks for the x87 and the SSE unit are
419     * the same.
420     */
421    __asm__ __volatile__ ("fnstcw %0" : "=m" (control));
422  
423    return (~control & FE_ALL_EXCEPT);
424  }

总结

通过对该文件的源码阅读,我们了解了浮点数异常处理机制和舍入机制的设置,实际上在 x86_64 架构下,有两个信息会有影响,即 fenv 信息、MXCSR 寄存器,它们都有对应的汇编指令进行读取和写入,我们上层的函数操作也是基于对这两种信息的读取、修改、写入上,实际还是汇编代码的操作。同时,在这个过程中也有相当多的位运算方式,通过位运算方式能够有效与寄存器交互,加快我们的运算效率。