引言
cfenv 是 C++对 C 语言头文件 fenv.h 的封装,该头文件定义了一系列与浮点数运算环境相关的函数和宏定义,以及一些相关的结构体定义。它的作用主要是控制程序运行过程中浮点数运算的状态 flag 和控制模式,接下来我们来看看这个头文件的具体作用与实现原理。
注一:下面的源码参考 android-12.0.0_r3 中的源码,具体代码路径为:
http://www.aospxref.com/android-12.0.0_r3/xref/
注二:贴近底层的代码实现通常都与具体的计算机体系结构相关,如:aarch64、arm、i386、x86_64 等,文中以笔者的运行环境为主,主要讲述 x86_64 架构相关的代码实现与分析。
C++的封装逻辑
参考对应文件http://www.aospxref.com/android-12.0.0_r3/xref/external/libcxx/include/cfenv中的代码片段如下:
56 #include <__config>
57 #include <fenv.h>
58
59 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
60 #pragma GCC system_header
61 #endif
62
63 _LIBCPP_BEGIN_NAMESPACE_STD
64
65 using ::fenv_t;
66 using ::fexcept_t;
67
68 using ::feclearexcept;
69 using ::fegetexceptflag;
70 using ::feraiseexcept;
71 using ::fesetexceptflag;
72 using ::fetestexcept;
73 using ::fegetround;
74 using ::fesetround;
75 using ::fegetenv;
76 using ::feholdexcept;
77 using ::fesetenv;
78 using ::feupdateenv;
79
80 _LIBCPP_END_NAMESPACE_STD
81
82 #endif // _LIBCPP_CFENV
复制代码
实际上这里做了两件事情,一是 include 了 C 语言的头文件“fenv.h”,二是,将头文件中全局的结构体、对应的函数都使用 using 语句修饰,保证在对应 std 命名空间中都可以正常访问到。这便是 C++对 C 头文件的封装,具体实现还是 C 语言头文件中的。
fenv.h 的结构体定义与宏定义
我们先来看看定义部分:
//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/include/fenv.h
34 #if defined(__aarch64__) || defined(__arm__)
35 #include <bits/fenv_arm.h>
36 #elif defined(__i386__)
37 #include <bits/fenv_x86.h>
38 #elif defined(__x86_64__)
39 #include <bits/fenv_x86_64.h>
40 #endif
...
42 __BEGIN_DECLS
//上面三个平台的函数定义
78 __END_DECLS
...
80 #if defined(__arm__)
81 #include <android/legacy_fenv_inlines_arm.h>
82 #endif
复制代码
这里通过不同的平台宏进行区分,最后选择了不同的平台头文件,我们以__x86_64__进行分析,其它平台类似(注意,__arm__平台有些区别,其它三个平台的头文件只定义了结构体或者变量,所以需要在该文件中定义函数,但是__arm__平台在其头文件中同时定义了结构体变量和函数,就不需重复定义了)
FE_xxx 异常宏
定义了 float 运算的相关类别,按位进行标识。
33 /*
34 * Each symbol representing a floating point exception expands to an integer
35 * constant expression with values, such that bitwise-inclusive ORs of _all
36 * combinations_ of the constants result in distinct values.
37 *
38 * We use such values that allow direct bitwise operations on FPU/SSE registers.
39 */
40 #define FE_INVALID 0x01
41 #define FE_DENORMAL 0x02
42 #define FE_DIVBYZERO 0x04
43 #define FE_OVERFLOW 0x08
44 #define FE_UNDERFLOW 0x10
45 #define FE_INEXACT 0x20
46
47 /*
48 * The following symbol is simply the bitwise-inclusive OR of all floating-point
49 * exception constants defined above.
50 */
51 #define FE_ALL_EXCEPT (FE_INVALID | FE_DENORMAL | FE_DIVBYZERO | \
52 FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT)
复制代码
FE_xxx 浮点数舍入方向宏
定义了浮点数进行舍入时的方向,按位进行标识(因为浮点数有精度限制,现有的编码方式并不能表示所有的数,需要考虑进行舍入,即使用一个可以的编码来近似表示该浮点数)。
FE_TONEAREST:将 x 舍入为最接近 x 的值,有一半的 case 舍入为 0
FE_DOWNWARD:将 x 舍入为不大于 x 的最大值
FE_UPWARD:将 x 舍入为不小于 x 的最小值
FE_TOWARDZERO:将 x 舍入为绝对值不大于 x 的最接近 x 的值
54 /*
55 * Each symbol representing the rounding direction, expands to an integer
56 * constant expression whose value is distinct non-negative value.
57 *
58 * We use such values that allow direct bitwise operations on FPU/SSE registers.
59 */
60 #define FE_TONEAREST 0x000
61 #define FE_DOWNWARD 0x400
62 #define FE_UPWARD 0x800
63 #define FE_TOWARDZERO 0xc00
复制代码
fenv_t 类型定义
该结构体实际上就是整个浮点数运算环境的定义,包括控制字、状态字、标志字等信息。
65 /*
66 * fenv_t represents the entire floating-point environment.
67 */
68 typedef struct {
69 struct {
70 __uint32_t __control; /* Control word register */
71 __uint32_t __status; /* Status word register */
72 __uint32_t __tag; /* Tag word register */
73 __uint32_t __others[4]; /* EIP, Pointer Selector, etc */
74 } __x87;
75 __uint32_t __mxcsr; /* Control, status register */
76 } fenv_t;
复制代码
fexcept_t 类型定义
异常状态类型信息,具体到函数使用时说明。
78 /*
79 * fexcept_t represents the floating-point status flags collectively, including
80 * any status the implementation associates with the flags.
81 *
82 * A floating-point status flag is a system variable whose value is set (but
83 * never cleared) when a floating-point exception is raised, which occurs as a
84 * side effect of exceptional floating-point arithmetic to provide auxiliary
85 * information.
86 *
87 * A floating-point control mode is a system variable whose value may be set by
88 * the user to affect the subsequent behavior of floating-point arithmetic.
89 */
90 typedef __uint32_t fexcept_t;
复制代码
fenv.h 的变量定义
定义了一个全局只读变量__fe_dfl_env,即我们上面说的 fenv_t 结构体,保存浮点数运算的相关环境设置信息;同时定义了一个获取该变量地址的宏 FE_DFL_ENV,使用取地址符,返回变量地址。
67 /*
68 * The following constant represents the default floating-point environment
69 * (that is, the one installed at program startup) and has type pointer to
70 * const-qualified fenv_t.
71 *
72 * It can be used as an argument to the functions that manage the floating-point
73 * environment, namely fesetenv() and feupdateenv().
74 */
75 extern const fenv_t __fe_dfl_env;
76 #define FE_DFL_ENV (&__fe_dfl_env)
复制代码
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 的实现,具体该选择哪一个呢?我们来看看具体的编译情况。
http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libm/Android.bp
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 的实现,接下我我们就分析该文件中的具体实现。
http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libm/amd64/fenv.c
管理异常 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
相关的汇编操作与 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
获取对应的异常信息,注意,这里从两个地方获取之后做了与操作
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 }
复制代码
管理浮点数舍入方向
管理整体环境变量
管理异常整体控制
评论