April 29, 2024, 12:31:36 AM

News:

IonicWind Snippit Manager 2.xx Released!  Install it on a memory stick and take it with you!  With or without IWBasic!


Returning FLOAT's

Started by Barney, July 08, 2007, 05:44:23 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Barney

I am using new Blitz3D SDK together with EBasic and while converting various examples I may have found an error in EBasic compiler. Here's what's bothering me. After creating a mesh (in this case a simple quad) I am using bbVertexX, bbVertexY and bbVertexZ functions (stored in a DLL file) to find the respective vertex coordinates. All three functions are taking two int parameters and are returning a float value for the coordinate. Unfortunately when I compile the program in EBasic all I am getting back is gibberish i.e. usually it's just 0.0f value but sometime it may be anything. The reason why I did not report this on Blitz3D forums first is simple. When I compile the same example in Aurora, Power Basic, BlitzMax and Pure Basic I get proper results and all the time I am using the same DLL. So obviously something strange is happening with EBasic compilation. I even tried the IBasic Pro and got the same bad result as with EBasic.

Here are the example source files in EBasic and Aurora:

EBasic

$USE "b3d.lib"

DECLARE CDECL IMPORT, bbBeginBlitz3D(),INT
DECLARE CDECL IMPORT, bbEndBlitz3D()
DECLARE CDECL IMPORT, bbKeyHit(n:INT),INT
DECLARE CDECL IMPORT, bbGraphics3D(width:INT, height:INT, OPT depth=0:INT, OPT mode=0:INT)
DECLARE CDECL IMPORT, bbFlip(OPT vwait=1:INT)
DECLARE CDECL IMPORT, bbUpdateWorld(OPT elapsed=1.0f:FLOAT)
DECLARE CDECL IMPORT, bbRenderWorld(OPT tween=1.0f:FLOAT)
DECLARE CDECL IMPORT, bbCreateMesh(OPT p=0:INT),INT
DECLARE CDECL IMPORT, bbCountSurfaces(m:INT),INT
DECLARE CDECL IMPORT, bbGetSurface(m:INT, index:INT),INT
DECLARE CDECL IMPORT, bbCreateSurface(m:INT, OPT b=0:INT),INT
DECLARE CDECL IMPORT, bbAddVertex(s:INT, x:FLOAT, y:FLOAT, z:FLOAT, OPT tu=0.0f:FLOAT, OPT tv=0.0f:FLOAT, OPT tw=1.0f:FLOAT),INT
DECLARE CDECL IMPORT, bbAddTriangle(s:INT, v0:INT, v1:INT, v2:INT),INT
DECLARE CDECL IMPORT, bbCountVertices(s:INT),INT
DECLARE CDECL IMPORT, bbVertexX(s:INT, n:INT),FLOAT
DECLARE CDECL IMPORT, bbVertexY(s:INT, n:INT),FLOAT
DECLARE CDECL IMPORT, bbVertexZ(s:INT, n:INT),FLOAT
DECLARE CDECL IMPORT, bbCreateCamera(OPT p=0:INT),INT
DECLARE CDECL IMPORT, bbCreateLight(OPT type1=0:INT, OPT p=0:INT),INT
DECLARE CDECL IMPORT, bbPositionEntity(e:INT, x:FLOAT, y:FLOAT, z:FLOAT,OPT global1=0:INT)
DECLARE CDECL IMPORT, bbRotateEntity(e:INT, p:FLOAT, y:FLOAT, r:FLOAT, OPT global1=0:INT)
DECLARE CDECL IMPORT, bbText(x:INT, y:INT, t:STRING, OPT centre_x=0:INT, OPT centre_y=0:INT)

'openconsole
bbBeginBlitz3D()
bbGraphics3D(800,600,32,2)

INT camera,sprite,s,vx

camera=bbCreateCamera()
bbPositionEntity(camera,0,0,-5)

' Create blank mesh
sprite=bbCreateMesh()
s=bbCreateSurface(sprite)
bbAddVertex(s,0,0,0 ,0,0): bbAddVertex(s,2,0,0 , 1,0)
bbAddVertex(s,0,-2,0 ,0,1): bbAddVertex(s,2,-2,0 , 1,1)
bbAddTriangle(s,0,1,2): bbAddTriangle(s,3,2,1)
bbPositionEntity(sprite,0,0,0)

DO
bbRenderWorld()
vx=bbVertexX(s,0)
bbFlip()
UNTIL bbKeyHit(1)

bbEndBlitz3D()
'closeconsole
END


Aurora

#USE "b3d.lib"

DECLARE CDECL IMPORT, bbBeginBlitz3D(),INT;
DECLARE CDECL IMPORT, bbEndBlitz3D();
DECLARE CDECL IMPORT, bbKeyHit(n as INT),INT;
DECLARE CDECL IMPORT, bbGraphics3D(width as INT, height as INT, OPT depth=0 as INT, OPT mode=0 as INT);
DECLARE CDECL IMPORT, bbFlip(OPT vwait=1 as INT);
DECLARE CDECL IMPORT, bbUpdateWorld(OPT elapsed=1.0f as FLOAT);
DECLARE CDECL IMPORT, bbRenderWorld(OPT tween=1.0f as FLOAT);
DECLARE CDECL IMPORT, bbCreateMesh(OPT p=0 as INT),INT;
DECLARE CDECL IMPORT, bbCountSurfaces(m as INT),INT;
DECLARE CDECL IMPORT, bbGetSurface(m as INT, index as INT),INT;
DECLARE CDECL IMPORT, bbCreateSurface(m as INT, OPT b=0 as INT),INT;
DECLARE CDECL IMPORT, bbAddVertex(s as INT, x as FLOAT, y as FLOAT, z as FLOAT, OPT tu=0.0f as FLOAT, OPT tv=0.0f as FLOAT, OPT tw=1.0f as FLOAT),INT;
DECLARE CDECL IMPORT, bbAddTriangle(s as INT, v0 as INT, v1 as INT, v2 as INT),INT;
DECLARE CDECL IMPORT, bbCountVertices(s as INT),INT;
DECLARE CDECL IMPORT, bbVertexX(s as INT, n as INT),FLOAT;
DECLARE CDECL IMPORT, bbVertexY(s as INT, n as INT),FLOAT;
DECLARE CDECL IMPORT, bbVertexZ(s as INT, n as INT),FLOAT;
DECLARE CDECL IMPORT, bbCreateCamera(OPT p=0 as INT),INT;
DECLARE CDECL IMPORT, bbCreateLight(OPT type1=0 as INT, OPT p=0 as INT),INT;
DECLARE CDECL IMPORT, bbPositionEntity(e as INT, x as FLOAT, y as FLOAT, z as FLOAT,OPT global1=0 as INT);
DECLARE CDECL IMPORT, bbRotateEntity(e as INT, p as FLOAT, y as FLOAT, r as FLOAT, OPT global1=0 as INT);
DECLARE CDECL IMPORT, bbText(x as INT, y as INT, t as STRING, OPT centre_x=0 as INT, OPT centre_y=0 as INT);

global sub main(){
bbBeginBlitz3D();
bbGraphics3D(800,600,32,2);

INT camera,sprite,s,vx;

camera=bbCreateCamera();
bbPositionEntity(camera,0,0,-5);

// Create blank mesh
sprite=bbCreateMesh();
s=bbCreateSurface(sprite);
bbAddVertex(s,0,0,0 ,0,0); bbAddVertex(s,2,0,0 , 1,0);
bbAddVertex(s,0,-2,0 ,0,1); bbAddVertex(s,2,-2,0 , 1,1);
bbAddTriangle(s,0,1,2); bbAddTriangle(s,3,2,1);
bbPositionEntity(sprite,0,0,0);

while (!bbKeyHit(1)){
bbRenderWorld();
vx=bbVertexX(s,0);
bbFlip();
}

bbEndBlitz3D();
return;
}


I finally took a look at the machine code and found some differences in the code directly responsible for calling the VertexX function and accepting the return float result. Here are the relevant asm lines:


' EBasic ASM

vx=bbVertexX(s,0)

mov eax, $vx
push eax
xor eax,eax
push eax
mov eax, $s
push dword [eax]
call [__imp_bbVertexX]
add esp,8
mov edx,eax
pop esi
push edx
fld dword[esp]
fistp dword[esp]
pop edx
mov [esi],edx

' Aurora ASM

vx=bbVertexX(s,0);

add esp,4
lea eax,[ebp-4]
push eax
xor eax,eax
push eax
lea eax,[ebp-8]
push dword [eax]
call [__imp_bbVertexX]
add esp,8
sub esp,4
fstp dword [esp]
pop eax
mov edx,eax
pop esi
push edx
fld dword[esp]
fistp dword[esp]
pop edx
mov [esi],edx
mov eax,1
mov edx,eax
push edx


My Intel assembly is very rusty and perhaps I am barking up a completely wrong tree but I'd be much obliged if someone can at least point me into some direction regarding the solution. Perhaps it's a bug in the way Blitz3D SDK DLL is setup but I doubt it because it works with all other compilers I've used. I am more inclined to think that it is something with EBasic and in that case I'd be very disappointed if it turns out that I can't use my favourite compiler with this SDK. I know I can use Aurora but EBasic is still the preferred solution. As long as this is not sorted out I cannot release the include file and the examples that have already been prepared by me and Todd Riggins. I am sure a lot of people would find Blitz3D SDK a great solution for their EBasic 3D needs.

Barney

Ionic Wind Support Team

Because in the real world 32 bit floats are never returned.  They are elevated to DOUBLE and saved in ST0, not returned in registers.  Aurora accounts for this automatically when you declare an import as CDECL, to stay in line with current standards. 

Aurora is a much different compiler than Emergence, a much better one as well, even if you haven't realized it yet ;)

For Emergence just change the return types to DOUBLE on the declares and they should work.  You see Emergence does allow returning 32 bit floats in a register, which turned out to be unique in the compiling world.  Learn assembler, it will save you a lot of time ;)

Emergence:

        call [__imp_bbVertexX]
        add esp,8   = Remove the pushed values from the stack
   mov edx,eax = Move the float returned in eax into edx
   pop esi = ESI is the address of vx
   push edx = Push the float onto the stack
   fld dword[esp] = Load it into the FPU
   fistp dword[esp] = Convert it into an integer (since vx is an integer)
        pop edx = Integer back into EDX from the stack
   mov [esi],edx = Store the integer into vx

Since the blitz DLL was obviously compiled with C the floating point value isn't in EAX at the exit of the function.  Instead it is in the FPU ST0 register.  So if you look at the Aurora code:

        call [__imp_bbVertexX]  Call the DLL function
        add esp,8 = Remove the pushed values from the stack
   sub esp,4 = Make room to store the float on the stack from the FPU
   fstp dword [esp] = Tell the FPU to store ST0 as a 32 bit float
   pop eax = Move the 32 bit float into EAX
   mov edx,eax = And then into EDX for safe keeping
   pop esi = Get the address of vx
   push edx = Push the float onto the stack
   fld dword[esp] = Load it into the FPU for conversion
   fistp dword[esp] = Convert it to an integer
   pop edx = Move it back into edx
   mov [esi],edx = And finally save the integer into vx

Now of course looking at compiler generated assembly code can give you headaches as hand written code is more streamlined, but it gets the job done.  The bottom line is Emergence is actually expecting the FLOAT to be left in EAX just like any other 32 bit value. However C dictates something different.  Telling Emergence the return is a DOUBLE should generate almost the same code as Aurora.


Paul.
Ionic Wind Support Team

Todd Riggins

Brought to you buy: http://www.exodev.com

Barney

Thank you for the explanation and a quick solution, Paul. Everything is working as expected now. I hope I'll have all the necessary bits and pieces ready soon. Perhaps more people would be interested in EBasic once they are able to connect it to tried and tested Blitz3D engine.

I do understand that Aurora is better compiler but I still prefer to work with BASIC like syntax. I was never fond of C and C like languages. FORTRAN, HP BASIC,FORTH and Motorola 68000 assembler were always my favourites.

Thanks again.

Barney

Todd Riggins

Hi Paul,

I was curious why you wouldn't code bit and pieces like that into EBasic like how Aurora does it? I think I  understand the reasoning you explained about the 32 bits stuff, but even the C header file for the sdk have the return values as floats. So for me, someone who doesnt know assembly, I would never figure that out on my own. I have the mindset if something like that would work with Aurora( or even with other programming languages), it should work the same with EBasic by keeping it all the same with the FLOAT datatype instead of having to be different and use the DOUBLE datatype.

Still though, thanks for explaining above and giving a solution to the problem. It's cool to see the b3d code work now in EBasic.

Brought to you buy: http://www.exodev.com

Ionic Wind Support Team

The problem isn't Emergence, it's the C declarations.   C evolved from it's 8 bit roots with a few quirks that all of us language authors have had to bend to.  For example consider the C declaration of:

float SomeFunc(float x);

32 bit floats are never used when passing parameters and the C compiler automatically elevates x to a double before pushing it on the stack, unless you use the STDCALL prefix.   So in essance in C

double SomeFunc(double x);

is identical to the first declaration as far as the C compiler is concerned.  It is a gross inefficiency having to convert and push 64 bits when only 32 is specified so when I originally designed IBasic I didn't wan't the inefficiency unless you were actually calling a C dll.  So IBasic used STDCALL by default.  When declaing an import as CDECL in IBasic:

declare cdecl import, SomeFunc(float x), float

The compiler correctly elevates x to a double before passing, but I missed the part of the C standard declaration that required a float return to be stored in ST0 instead of EAX.  If you have ever read the entire C99 draft specs you would give me a bit of slack on missing that bit ;)

Emergence BASIC was intended to be 100% backwards compatible with IBasic Pro, so I left it in there so you could still use your DLL's made with IBasic.

Aurora was a new compiler design from scratch using the knowledge I gained from my time with IBasic.  And I don't need to be compatible with anything out there.  I did use the same vtable design that C++ uses only because I was quite familiar with it. And I am more comfortable with languages that use braces to indicate BEGIN and END so I used that as well.  Which is probably why it is compared to C so much.

Paul.
Ionic Wind Support Team

Todd Riggins

Quote from: Paul Turley on July 08, 2007, 08:32:20 PM
If you have ever read the entire C99 draft specs you would give me a bit of slack on missing that bit ;)

No slack! Hop to it! just kidding ;)

That makes sense. I do apprieciate the time you take in explaining things in detail like that. It's interesting for me to read info like that and helps me learn more on how things work under the hood.

Thanks
Brought to you buy: http://www.exodev.com

efgee

Quote from: Paul Turley on July 08, 2007, 08:32:20 PMAurora was a new compiler design from scratch using the knowledge I gained from my time with ...
I did use the same vtable design that C++ uses only because I was quite familiar with it.

Should this indicate that because C++ vtable is the same as the one of Aurora a Aurora user can use C++ DLL's and libraries?

Ionic Wind Support Team

Don't read too much into that.  Aurora is not C++, it is a language of my own design.   While it might be possible to use a static class library created with C++, and get it to link, you would have to deal with all of the oddball name mangling that various C++ compilers use.  For instance you can't use a class library compiled with GCC and get it to work with MSVC++ or Borland for that matter. 

There is no standard for name mangling and compiler writers are free to use whatever works with their compiler.  You can use static libraries containing C functions, I do it in both Emergence and Aurora for the browser functions which are written with C++.  It's just the exports that are normal functions.

Really the reason for using a standard vtable design has nothing to do with libraries and DLL's anyway.  It is the way virtual function pointers of a class are stored and how they are indexed.  And the one Aurora uses was to make using COM much easier, since a MS C++ vtable and a COM interface are laid out identically.

However knowing that if you exported an accessor function that returned a pointer to a class you could, in theory, create a class definition in Aurora that matched it and the actual method names wouldn't matter.  Provided the C++ class only contained virtual methods. 

EOT (End of Transmission)

Paul.
Ionic Wind Support Team