Search code examples
assemblyx86masmmasm32x87

What is the correct way to access Tag Word in FPU?


Assembly is the first programming language I am learning and I have read both the Intel IA-32 manual and http://www.ray.masmcode.com/tutorial/fpuchap3.htm#fstenv sections related to the FPU. I found the memory layout for 32-bit:Here

According to this chart above the tag word is taking up 16 bits on address 8. Unfortunately the Intel guide nor ray's guide used a syntax example.

My issue is that I WAS getting 1#IND when I load fld $Tvalueinc[ecx] into st(0) during the 2nd loop.

To attempt to figure out why I get #1INF, I dug around and found you could look at FPU status using the TAG WORD register(http://www.ray.masmcode.com/tutorial/fpuchap1.htm).

Using this section of code:

fstenv [ebx-16]
fwait
fldenv [ebx-16]
mov    eax, [ebx-16]

If the code worked the data should change each loop, but it stays the same.

What am I doing wrong trying to access the FPU? You don't have to worry about the point of my code, just why I why i am unable to access the TAG WORD.

    .386
.model flat, stdcall
option casemap :none  

includelib \masm32\lib\msvcrt.lib
sprintf proto C :vararg
includelib \masm32\lib\user32.lib 
MessageBoxA proto :ptr,:ptr,:ptr,:DWORD
includelib \masm32\lib\kernel32.lib
ExitProcess proto :dword 

.data
   _title db "Result",13,10,0
   $interm db "%0.4f","+","%0.5f",13,10,0
   Aval REAL8 1.000
   Bval REAL8 -2.000
   Cval REAL8 19.000
   _fourval REAL8 4.000
   $Tvalueinc REAL4 1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0
   $sampleval real10 4478784.0
   squareroot dq ?
   $prevCW dw ?
   $Tagword dd ?




.code
main PROC
LOCAL szBuf[9]:byte
fstcw $prevCW
fwait
fld Bval ;  [loads first instance of b]]
fmul Bval ; [b*b = b^2]
fld Aval ;[Load a (a*c)]
fmul Cval ;(a*c)
fmul _fourval ;[4*a*c]
fsubp;[b^2-4*a*c]
ffree st(7)
ftst ;compare ST(0) with 0.0
fstsw ax ;[store camparison results in ax]
fwait;wait
sahf ;transfer flags from AH register
mov ecx, 04h
fld $sampleval

jb _negative ;jump if <0
fsqrt ;sqrt(b^2-4*a*c)
jmp Finished



_negative:


$repeat:
mov ax, $prevCW


push eax
fld $Tvalueinc[ecx]
fstenv [ebx-16]
fwait
fldenv [ebx-16]
mov eax,[ebx-16]
fdiv
fldcw [esp]

fstsw ax

FRNDINT
fldcw $prevCW
pop eax


jne $repeat






Finished:  

   fstp squareroot
   mov eax, dword ptr squareroot
   mov edx, dword ptr squareroot[4h]
   invoke sprintf, addr szBuf, offset $interm, eax, edx
   invoke MessageBoxA, 0, addr szBuf, offset _title, 0
   invoke ExitProcess, 0



main ENDP
END main

Note: I use Visual Studio 2017 and the debugger keeps popped values shown for some reason.


Solution

  • The control-word and the status-word are stored before the tag word, according to the diagram you linked. (But note that it's the real-mode layout. You're running in 32-bit protected mode. (Actually in compat mode if you're running 32-bit code under a 64-bit OS, but it's essentially identical to 32-bit protected mode under a 32-bit OS). mov eax, [ebx-16] will load the control word, plus whatever is in the high 16 bits. (IDK if it's guaranteed to be zero or not.)

    Try movzx eax, word ptr [ebx - 8] to load the low half of the 3rd four-byte double-word counting from ebx-16.

    fstenv    [ebx-16]
    ;fwait                  ; you don't need this
    ;fldenv    [ebx-16]     ; you don't need to do this.
    mov eax,  [ebx-16]      ; loads the x87 control word into eax, plus padding
    

    Try this instead:

    fnstenv     [ebx-16]
    movzx       eax, word ptr [ebx-16 + 8]  ; should be the tag word if that's the right diagram
    

    FP exceptions (like denormal or invalid result) are all masked by default at process startup, so they only set bits in the x87 status word rather than actually jumping to an exception handler when you do something like divide by zero. Since fstenv only modifies the FP environment by masking all exceptions, you don't need to restore it with fldenv.


    Assembly is the first programming language I am learning

    I'd highly recommend learning some C, so you can easily look at C compiler asm output to see how the compiler does something that you understand in C. (e.g. on http://gcc.godbolt.org/)

    Also, I'd recommend against spending much time on x87 FP, because it's weird and obsolete. Nothing else in x86 uses a register stack with tags to say which ones are valid. Modern x86 code does FP math using SSE2 instructions.

    See the tag wiki for lots of good links.

    I use Visual Studio 2017 and the debugger keeps popped values shown for some reason.

    It should show you some kind of indication of whether the registers are tagged "free" or in-use.

    Marking a register free is separate from changing the value in it. The x87 stack uses tags to keep track of which underlying register is actually the current top of the stack, and which ones are free. Modifying these tags doesn't clear the data, and I guess fstenv still stores the value to memory where the debugger can get it.

    My issue is that I WAS getting 1#IND when I load fld $Tvalueinc[ecx] into st(0) during the 2nd loop.

    IIRC, that happens when the x87 stack is already full.