存储 x87 FPU 控制字

Storing the x87 FPU Control Word

我正在制作 returns FPU 控制字寄存器(16 位)的函数。
根据文档,我必须使用 fstcw 和一个内存位置。

我记忆中的位置是:

fpuctl: .word 0

而我的函数是:

.global getFPUControlState
    .type getFPUControlState, function
    getFPUControlState:
    pushl %ebp
    movl %esp, %ebp 
    xorl %eax, %eax #clear eax (ax too)

    fnstcw fpuctl #store in fpuCTL
    mov fpuctl, %ax #put it in 8bit %ax

    leave
    ret

控制台显示:

Memory protection violation.

如何正确使用fnstcw

TL:DR:您可能将 fpuctl: .word 0 与您的代码一起放在只读 .text 部分。存储到堆栈上的一些划痕 space,或者如果你真的想使用静态存储,则存储到 BSS。


你说得对,the only form of fnstcw 是记忆目标。 更常用的 fnstsw %ax(x87 status 词)具有 AX 目标形式,但 fnstcw 没有。

(当然,x87 已过时,除非您实际需要 80 位精度;modern x86 has at least SSE2 so you can and should do scalar FP math in XMM registers。SSE FPU 的控制和状态位都在 MXCSR 中,与 x87 控制和状态分开。)

另请注意,如果您从 C 调用它并要修改 x87 控制字,您需要将其告知编译器 所以它不假设舍入模式仍然是最接近的舍入,或者精度仍然是 80 位(64 位尾数)。这对于编译时常量传播和其他优化可能很重要。例如,gcc 有 -frounding-math-fsignaling-nans。它也可能支持 C99 #pragma STDC FENV ACCESS ON,但我不确定 gcc 或 clang 是否完全符合标准。另见 https://gcc.gnu.org/wiki/FloatingPointMath. (You should use standard C functions from fenv.h 修改 FP 环境,如 fegetenv / fesetenv。)

如果您要从手写的 asm 中使用它,请将其设为一个宏,将第二个参数作为临时内存位置,而不是函数。这太小了,作为一个非内联函数没有意义;这是 2 条有用的说明(fnstcw 和重新加载);剩下的是开销。


顺便说一句,AX 是一个 16 位寄存器。 AL是EAX的低8位。

此外,movzwl fpuctl, %eax 会执行零扩展字加载,因此您不需要先对 eax 进行异或零处理。


您没有提供 MCVE,但是 可能您将 fpuctl: .word 0 与您的代码一起放在了只读 .text 部分, 所以您得到与使用 mov %eax, getFPUControlState.

相同的错误

相反,将其放入 BSS(零初始化静态存储,其中零不存储在您的可执行文件中,只是总大小)。

.bss
fpuctl: .zero 2         # 2 bytes of zeros in the BSS

.text                   # switch back to the .text section
.globl getFPUControlState         # GAS does accept .global as a synonym for .globl
.type getFPUControlState, function
getFPUControlState:
       # leave out the EBP stack-frame stuff, you're not using it for anything
    fnstcw   fpuctl
    movzwl   fpuctl, %eax       # zero-extending word load into EAX

    ret
.size getFPUControlState, . - getFPUControlState

作为替代方案,use .lcomm to reserve 2 bytes in the BSS 并使用 fpuctl 将其标记为非导出标签:

.lcomm  fpuctl, 2     # puts stuff in the BSS regardless of current section

但实际上,您根本不需要静态存储

所以您可以通过使用堆栈来避免潜在的页面错误。

.globl getFPUControlState
.type getFPUControlState, function
getFPUControlState:

    sub      , %esp          # reserve space on the stack
    fnstcw   (%esp)
    movzwl   (%esp), %eax
    add      , %esp          # restore ESP to point at the return address

    ret

或者,如果您宁愿以在单词存储之后从双字加载中存储转发停顿为代价来优化代码大小:

    push     [=13=]
    fnstcw   (%esp)
    pop      %eax         # upper 2 bytes are zero from the PUSHed data
    ret