News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

FPU - fild QWORD PTR problem

Started by Phoenix, December 26, 2004, 12:55:56 AM

Previous topic - Next topic

Phoenix

Hi all,

i am trying to implement time measurement using QueryPerformance-API. The values are returned as LARGE_INTEGER (QWORD). Now, when i try to load the results to FPU-registers, everything works fine as long as only two QWORDs are loaded with fild-instruction. When the third one is enabled, something goes wrong and the client area of my window is only redrawn when the mouse cursor is over the title bar. It does not matter, which instruction is commented out... i have checked the values of each QWORD, and everything seems to be correct. finit is also used before the instructions. so are there limitations with qwords?

   
;fild QWORD PTR lpFreq.QuadPart
fild QWORD PTR lpPCount_1.QuadPart
fild QWORD PTR lpPCount_2.QuadPart



russian

#1
 that looks weird...I actually wrote something really similar lately. This code is a timer, call SpeedInit once and it will fill your Ticks variable. Then call SpeedStart and SpeedStop as much as you like as long as you pass the value you got from SpeedInit(*Ticks) as the second argument to SpeedStop. The 3rd parameter in SpeedStop specifies the time measurement you want (1 for seconds, 1000 for milliseconds, 1000000 for microseconds, etc...)

   
SpeedInit   proc    pTicks:DWORD

    inv     QueryPerformanceFrequency, pTicks
    test    eax, eax
    jz      short @f
        mov     eax, -1   
    ret
    @@:
    xor     eax, eax

    ret

SpeedInit   endp

SpeedStart  proc pCount:DWORD

    inv     QueryPerformanceCounter, pCount
    ret

SpeedStart  endp

SpeedStop   proc pCount:DWORD, pTicks:DWORD, Multiplier:DWORD 

    LOCAL   newcount:_LARGE_INTEGER

    inv     QueryPerformanceCounter, ADDR newcount                   
    mov     edx, pCount
    mov     eax, pTicks
    finit
    fild    QWORD PTR newcount
    fild    QWORD PTR [edx]
    fsub
    fild    QWORD PTR[eax]
    fdiv
    fild    DWORD PTR Multiplier
    fmul
    fistp   QWORD PTR[edx]
   
    ret
   
SpeedStop   endp

   
             

Hope this help!
edited because of bad code. (tell me if it's still bad)

raymond

From the Win32 Programmer's Reference:
QuoteBOOL QueryPerformanceFrequency(

    LARGE_INTEGER *lpFrequency    // address of current frequency
   );   
When you invoke that function, you gave it the address of a DWORD (LOCAL   ticks:DWORD) and you also return the value as a DWORD in EAX. I realize it currently may be sufficient on most boxes including yours and mine but it may not be very portable in the near future.

Raymond
When you assume something, you risk being wrong half the time
http://www.ray.masmcode.com

raymond

Also forgot to mention that using the address of a local DWORD of a procedure to store a QWORD would overwrite the previous DWORD which may be most important. In this case, it would be the pushed EBP value which would be overwritten and that EBP register would not be restored properly on exit.

Raymond
When you assume something, you risk being wrong half the time
http://www.ray.masmcode.com

Phoenix

Raymond, thank you for your help! But sorry, i did not understand. Seems that there are wrong addressing modes in my code, so here are the snippets:


.DATA?

lpFreq LARGE_INTEGER <?>
lpPCount_1 LARGE_INTEGER <?>
lpPCount_2 LARGE_INTEGER <?>


WinMain Proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

        ; <other code here >
Invoke QueryPerformanceFrequency,ADDR lpFreq
        ; <other code here >

TimerProc proc hWnd:DWORD, uMsg:DWORD, idEvent:DWORD, dwTime:DWORD

Invoke QueryPerformanceCounter,ADDR lpPCount_1

;< some code here >

Invoke QueryPerformanceCounter,ADDR lpPCount_2

finit
push 1000
        fild DWORD PTR [esp]
fild QWORD PTR lpFreq.QuadPart
fild QWORD PTR lpPCount_1.QuadPart
fild QWORD PTR lpPCount_2.QuadPart
fsub st(0),st(1)
fdiv st(0),st(2)
        fmul st(0),st(3)
fist DWORD PTR [esp]
pop eax


Now i expected to have the elapsed time in ms as integer in eax, but this caused the mentioned effect. When i do a workaround like this, the effect disappears:

finit

;fild QWORD PTR lpFreq.QuadPart
fild QWORD PTR lpPCount_1.QuadPart
fild QWORD PTR lpPCount_2.QuadPart
fsub st(0),st(1)
fist DWORD PTR [esp]

mov ecx,1000
pop eax
mul ecx
lea ecx,lpFreq
mov ecx,[ecx]
div ecx


What is going wrong?

dioxin

Phoenix,
    at no point in your code do you pop the FP stack to remove the old operands.
    Instructions such as FSUB and FMUL  use 2 operands, overwrite one with the result and leave the other on the stack.
    So, when your code exits, the stack has 3 surplus values left on it.

   You might need to use the FSUBP and FMULP versions if the instruction which pop one value off the stack or explicitly clean up the stack before you exit

Paul.

russian

Quote from: raymond on December 26, 2004, 05:53:36 PM
Also forgot to mention that using the address of a local DWORD of a procedure to store a QWORD would overwrite the previous DWORD which may be most important. In this case, it would be the pushed EBP value which would be overwritten and that EBP register would not be restored properly on exit.

Raymond


thanks a lot!! I'll edit the code to correct this.

Phoenix

Quote from: dioxin on December 26, 2004, 07:31:49 PM
   You might need to use the FSUBP and FMULP versions if the instruction which pop one value off the stack or explicitly clean up the stack before you exit

Paul.

dioxin: Thank you for pointing out this. I always suggested that each routine using FPU calls finit first to do the cleanup of the stack. But, however, the strange behaviour happens as the third qword is loaded, with two operands everything works fine. Hm, think this could work: load two qwords, do the calculation with popping, load the next one.. i'll try this immideatly.

Phoenix

...and it works!!!  :U

Invoke QueryPerformanceCounter,Offset lpPCount_2
push 1000
finit
fild QWORD PTR lpPCount_2.QuadPart ; END: performance-counter value
fild QWORD PTR lpPCount_1.QuadPart ; START: performance-counter value
fsubp st(1),st ; st(0)=END-START
fild QWORD PTR lpFreq.QuadPart ; st(0)=lpFreq,st(1)=END-START
fdivp st(1),st ; st(0)=(END-START)/lpFreq
fild DWORD PTR [esp] ; st(0)=1000
fmulp st(1),st ; st(0)=(END-START)/lpFreq*1000
fistp DWORD PTR [esp] ; store milliseconds as integer
pop eax ; load ms to eax and cleanup stack


Thankyou all for your help. However, I'd like to know why this happend with three qwords in FPU-registers.....

dioxin

Phoenix,
QuoteI always suggested that each routine using FPU calls finit first to do the cleanup of the stack

You might do that, but Windows (or any other code around yours) might not. Depending on the context of your code, it's possible that the FPU stack already has important information on it which you destroy (by FINIT). It's also possible that the next procedure expects the stack to be clean and does not use FINIT so the satck then overflows because of the data you left on it.

Paul.

Phoenix

Quote from: dioxin on December 26, 2004, 11:09:17 PM

it's possible that the FPU stack already has important information on it which you destroy (by FINIT). It's also possible that the next procedure expects the stack to be clean and does not use FINIT so the satck then overflows because of the data you left on it.

Paul.

Yes, but if so do we have to call FSAVE/FRSTOR always when using FPU?

dioxin

Phoenix,
    if in doubt, always save the CPU and FPU states. e.g. if you call the Windows API within your code then that API call may require  FPU stack space or may use registers that you must preserve before the call and restore afterwards if you were usuing them in your own code.
   If your code is self contained then this may not be necessary, you as the programmer will know what needs to be preserved in your own code.

   Personally, regardless of any guidelines, I think you should ALWAYS preserve the CPU and FPU stacks when you call external code.

    There are standard Windows conventions which others here will know more about than me which guide you as to what should be saved/restored and what's put on the stacks when you call the Windows API or other external procedures. Often these are hidden by the compiler so you aren't directly aware of them but with ASM you manipulate registers directly so you need to be more careful.

Paul.

raymond

Phoenix

I've tried your "problematic" code half a dozen different ways and always got the same answer without any apparent problem. Loading more than 2 qwords to the FPU cannot be the only cause. There has to be something else in your code. Running your prog in a debugger with the 1st breakpoint where you compute the time and stepping through each of its instructions could give some more light on the subject.

Using finit before using the FPU is highly recommended unless you keep track of its content and do not call any API.

At least WindowXP is known to use the FPU with some of its functions and does not care about preserving it for other users. For that reason, the FPU can thus be considered as any of the registers which are not expected to be preserved for Windows functions.

Raymond
When you assume something, you risk being wrong half the time
http://www.ray.masmcode.com