News:

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

Floating point arithmetic and DLLs

Started by Merrick, February 22, 2005, 06:58:01 PM

Previous topic - Next topic

raymond

Merrick

My main recommendations when programming the FPU are:
- Fully understand the requirements and results of the instruction used.
- Comment each FPU instruction with the resulting content of each register based on the above knowledge.

I could certainly spoon-feed you with the correct code but you may not learn as fast. Study carefully the description of the fdiv and fsub (with and without operands) instructions in the following tutorial and write down the result of each FPU instruction of your four proc's (including the commented instructions). Then report back your findings. I will then comment further if necessary.

http://www.ray.masmcode.com/tutorial/appen1.htm

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

Merrick

Hi Raymond,

Thanks. I'll do that and post the results. I should say that I didn't realize I had posted the code with the explicit registers - I tried that code with implied registers (no registers named) and then with what I thought were the appropriate registers to see if that made a difference. I thought I had this right, but will definitely go check your suggestions.

Thanks!
Merrick

AeroASM

I find it less error-prone and easier to read if you do one fld, then use fadd, fsub etc with one memory operand and then fstp the result back off. Looking at your code, I think you have made several mistakes concerning the FPU stack, which is probably why Raymond told you to look up the FPU instructions.

By the way, Raymond, why does the FPU have a stack in the first place?

raymond

QuoteBy the way, Raymond, why does the FPU have a stack in the first place?

The word "stack" for the FPU registers should never have been used in the first place. It can only confuse newbies and does not provide the picture of the bottom of that "stack" being connected directly with the top of the same "stack". If you look at my tutorial, I describes it as the compartments of a revolving barrel. There's no revolving barrel either in the FPU either but at least it cannot be confused with anything on the CPU. ::)

It could also have been described as a chest containing 8 drawers where drawers would need to be shuffled downward and bring the bottom one to the top when loading a register and vice versa when popping it. But rotating a barrel seemed to require less energy. :bg

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

Merrick

Raymond, thanks for your advice. There were more mistakes in my code than should have been because I tried about 10 different things to get it to work before puttting it up and didn't recognize a couple of obvious mistakes I should have caught before putting the final version up. I have the original MASM 6.1 manuals and read the description of how the stack is handled in chapter 6, but misread the example for floating point arithmetic on page 143. It shows two memory locations, each of which is pushed onto the "stack" (I know, bad word!) and then shows an add operation which add the two number and leaves the result in st (st[0]). At least, that's what I *THOUGHT* it said. I didn't notice that after bothering to load the two values onto the stack the next instruction didn't add the two values it'd pushed onto the stack, but added the top stack value back to first memory location. I read that 10 times and read it wrong each time. In my head I assumed they loaded the two memory locations onto the stack so they could operate on them while on the stack (seemed to make sense!) and my eyes refused to see what was actually going on!

Thanks for your help. Here's the final code doing what I wanted - passing two integers by address and a double precision destination location by address also; the second integer is subtracted from the first integer, then the result of the subtraction is divided by the second integer and the double precision result is placed into the double precision destination addressed:

Divide2Ref proc numerator:dword, denominator:dword, result:dword
   finit
   fild dword ptr[numerator]
   fisub dword ptr[denominator]
   fidiv dword ptr [denominator]
   mov edx, result
   fst qword ptr[edx]
   ret
Divide2Ref    ENDP

(Oh yeah! I also forgot that I didn't realize there was a separate FISUB and FIDIV until looking again!)

Thanks for your help.
Merrick

raymond

QuoteThanks for your help.
Merrick

My pleasure.

A few comments for future consideration if you ever NEED to optimize for speed.

The finit instruction is slow. It should not be included in procedures which may be called often. It should preferably be used before you start a set of computations and want to insure that the FPU is then "clean".

Working with values already on the FPU is always faster than retrieving them from memory. It does take some time to (i) get them from memory and (ii) convert them to the REAL10 format of the FPU registers. For example, in your snippet, the denominator is brought in twice from memory. Using the following would have saved a few clock cycles:


   fild dword ptr[numerator]       ;a
   fild dword ptr[denominator]     ;b    a
   fsub st(1),st                   ;b    (a-b)
   fdiv                            ;(a-b)/b


And then, you should always clean up the FPU registers whenever you don't need the data anymore. The fst qword ptr[edx] should thus have been:

fstp qword ptr[edx]

which would have left all the FPU registers you used within the proc as FREE as when you entered the proc. (In other cases, you may want to leave the result on the FPU for further processing. This is called FPU register management.)

Keep practicing and you will rapidly become an expert FPU programmer.

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

zooba

And if you have a return variable set in Visual Basic to be a Single or Double, it will expect to find it on the FPU so leave 1 value on there (in ST(0)).

I can only assume that VB pops it off the FPU itself.

raymond

QuoteI can only assume that VB pops it off the FPU itself.

When you assume something, you risk being wrong half the time

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

zooba

Haha, very true.

However, there aren't too many options in this case. Passing memory pointers around in VB is an absolute pain and the only other reasonable way I could imagine to pass a floating-point value is into a VB-allocated section of memory which is passed to the function (rather than the function allocating it and passing a pointer back).

There would be documentation somewhere which specifies, no doubt. However, I don't have the time and/or patience to look it up  :wink

Merrick

zooba... huh?

Maybe I don't understand what you're referring to. That standard convention for VB calls is to pass memory pointers. If you want to pass values and not pointers you have to explicitly use the ByVal option in your declare statements.

zooba

No, it appears you don't. :wink Don't worry, it took me a while to learn all the ins-and-outs of VB->ASM programming.

I'll use an example. (Note that this code isn't useful :U)

This passes the single ByVal (hence lea eax, dwValue. passing it ByRef would just use a mov) and returns as a function
; Declare Function Times2 Lib "math.dll" (ByVal Value As Single) As Single

Times2 PROC dwValue:DWORD
    lea     eax, dwValue
    fld     DWORD PTR [eax]
    fld     st(0)
    fadd
    ret        ; Leaves the answer on the FPU for VB to deal with
Times2 ENDP


This passes the single ByVal and a solution ByRef. Note that it leaves the FPU clear and has no return value
; Declare Sub Times2 Lib "math.dll" (ByVal Value As Single, ByRef Solution As Single)

Times2 PROC dwValue:DWORD, lpSolution:DWORD
    lea     eax, dwValue
    fld     DWORD PTR [eax]
    fld     st(0)
    fadd
    mov     eax, lpSolution
    fstp    DWORD PTR [eax]
    ret
Times2 ENDP


I haven't discovered any noticable performance difference between the two styles (tested in the VB IDE with GetTickCount and a few million iterations) except I haven't found any guarentee that VB cleans the FPU up properly (and judging from a few random errors I get when using OpenGL, I'd say that it doesn't  ::))

Quote from: zooba on March 01, 2005, 11:57:12 PMPassing memory pointers around in VB is an absolute pain

Just to explain this (which is where I think you may be getting confused), you'd (ie. I would) never dream of returning a pointer as a Long to VB, because it provides no intrinsic functionality for dealing with it (API calls only). It also automatically deals with ByRef statements and treats them as the actual value rather than a pointer as ASM would. To illustrate that last point...

Sub MySub(Value As Long)
    MsgBox Value
End Sub


Sub MySub(ByVal Value As Long)
    MsgBox Value
End Sub


...both display an identical message box. Passing a memory pointer is totally transparent within VB and with most API calls which already include 'ByVal' where appropriate in their declarations. This means that receiving a memory pointer as a return value causes a lot of headaches (for an example which was giving me headaches until I wrote an ASM routine to deal with it, 'glGetString' returns a POINTER to a null-terminated string. VB just sees a Long [DWORD] value and can't recognise that there's a string at the end, and the string is in the wrong format. In ASM, it's trivial to move the address into ESI and just do a string copy until reaching the null character - VB provides no functionality for this)

Whoa, thats quite a lot - one of my longest posts yet. I s'pose I should consider writing a whole essay on this subject, shouldn't I  :8)

MichaelW

#26
This is a test of passing doubles from VB to a DLL and back to VB using both ByVal and ByRef. As coded (now after I removed a dumbass error :red) both methods work correctly (with VB5CCE, because I'm too lazy to install 6).

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
      .486                      ; create 32 bit code
      .model flat, stdcall      ; 32 bit memory model
      option casemap :none      ; case sensitive

      include \masm32\include\windows.inc
      include \masm32\include\masm32.inc
      include \masm32\include\kernel32.inc
      includelib \masm32\lib\masm32.lib
      includelib \masm32\lib\kernel32.lib
      include \masm32\macros\macros.asm
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
      .data?
        hInstance dd ?
      .code
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
LibMain proc instance:DWORD,reason:DWORD,unused:DWORD
    .if reason == DLL_PROCESS_ATTACH
      push instance
      pop hInstance
      mov eax, TRUE
    .elseif reason == DLL_PROCESS_DETACH
    .elseif reason == DLL_THREAD_ATTACH
    .elseif reason == DLL_THREAD_DETACH
    .endif
    ret
LibMain endp
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
ReturnDoublePassedByVal proc dbl:QWORD
    LOCAL localdbl:QWORD
    ; Verify high-order dword passed first.
    mov   eax,[ebp+12]
    mov   DWORD PTR localdbl+4,eax
    mov   eax,[ebp+8]
    mov   DWORD PTR localdbl,eax
    fld   localdbl
    ret
ReturnDoublePassedByVal endp

ReturnDoublePassedByRef proc ptrdbl:DWORD
    mov   eax,ptrdbl
    fld   QWORD PTR [eax]
    ret
ReturnDoublePassedByRef endp
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end LibMain

eschew obfuscation

AeroASM

Zooba,

In your previous example you used:


lea eax,dwValue
fld dword ptr [eax]


Why not just use:


fld dwValue

zooba

Simply because for some reason I don't trust the assembler to handle it correctly.  :bg

No doubt you can use the other way, but the one extra instruction isn't going to hurt at all.

AeroASM

I am horrified if I write something superfluous in ASM. I feel that because ASM is the best language to write in in terms of speed, you should always optimise as much as you can. If I don't feel like optimising, I write in VB.  :wink