Page 1 of 1

Proper C++ support in ee-gcc?

Posted: Tue Nov 16, 2004 3:33 pm
by stefan
What would it take to get fully functioning C++ support in GCC 3.2.2? I remember reading about crt0.s needing to call _init(), which seems easy enough to fix, but are there any other problems in the toolchain?

You don't have to be a toolchain hacker to respond, if you develop on PS2 using C++ and have run into problems please post them here. I'm willing to look into the major roadblocks so that we can have a robust C++ toolchain.

Thanks :)

Posted: Tue Nov 16, 2004 5:10 pm
by pixel
About _init and _fini, those should be internal symbols only. That is, the usual crt mess from gcc/glibc is modular and comes in multiple files. A module file (.so under linux) may provide _init and _fini symbols, and when compiling a .so file, it will use some other crt modules providing these symbols as weak. Now I may be wrong, of course. That is such a mess ;) Well, we may try to introduce them if "really necessary" (that is, if somebody exhibits me some code which is really needing that _init symbol to be called). Well, anyway, that's not a big issue to implement... Only having to call _init and _fini in our crt0, and provide them as weak symbols into crt0.o.

IMHO, the biggest issue to support "full" C++ is the STL which requires alot of libc support. And I am one to be willing to definitively get rid of newlib's libc (I think libm may stay). Now, I don't really like STL too, and I have no idea about the problems linked to missing libc functions to our ps2sdk's libc when using STL. I think it should require "full" stream support (we are lacking the unputc functions), but apart of that, I don't know ;)

Posted: Tue Nov 16, 2004 6:18 pm
by mrbrown
_init() and _fini() are used to initialize global/static constructors and call global deconstructors in C++, so they are definitely required for "proper" C++ support. GCC also has an extension for C, where you can use the function attribute "constructor" to register a C function to be called automatically at program startup. That seems like it could be useful.

If I were adding calls to _init() and _fini() in crt0, I'd make sure I didn't break compatibility with other toolchains that don't use the _init/_fini system (e.g. the 2.9x EE toolchain). IMO, they must be added, because asking C++ developers to call _init() at the top of main() exposes parts of the compiler the developer shouldn't have to know (or care) about, and it's silly to ask such a thing when the fix is obvious. Just make sure it's a clean fix.

If you are going to be using C++ on PS2, then I strongly advise against using STL for anything. STL has the nasty habit of allocating objects behind your back, and besides GCC 3.2.2 isn't terribly efficient at removing duplicate template instantiations (IIRC) so you could end up with horribly bloated code. If you really do need STL I'd recommend using STLport.

Anyway, those are the only C++ issues I know of. It's also advisable to compile without exceptions (-fno-exceptions), as including them without using them can generate unwanted references to GCC's internal libraries. Using -fno-rtti is a good idea too (if you are trying to use dynamic casts in C++ on PS2, then you're using "too much" C++).

IMO besides the issues mentioned above, C++ support is already solid in GCC 3.2.2.

Posted: Wed Nov 17, 2004 2:42 am
by Lukasz
Here is my solution to the global constructors fix or non dynamic created classes as N!K would say.

I came up with this since _init() was just a jr ra, nop function, maybe that is fixed now, posting it even so :)

In my link file I've put in ctors and dtors as shown below

Code: Select all

	.text 0x100000  : {
		*(.rodata)
		*(.text)
	}
	
	.ctors ALIGN(16): { _ctors = .; *(.ctors); }
	.dtors ALIGN(16): { _dtors = .; *(.dtors); }

	.reginfo ALIGN(128) : {
		*(.reginfo)
	}
In main() I have :

Code: Select all


typedef void (*cdtor)();

extern "C" 
{ 
	// Global constructors/destructors, values defined in linkfile
	extern void* _ctors;
	extern void* _dtors;
}	

static u32* g_ctors = (u32*)&_ctors;
static u32* g_dtors = (u32*)&_dtors;

int main()
{
	cdtor ctor = (cdtor)*g_ctors;
	cdtor dtor = (cdtor)*g_dtors;
	
	// Call global constructors
	if(ctor) ctor();
	
..
<insert code here>
..

	// Call global destructors
	if&#40;dtor&#41; dtor&#40;&#41;;

	return 0;
&#125;
This method isn't pretty, calling the constructors and destructors should really be done in crt0.s, it does however work :)

Posted: Wed Nov 17, 2004 3:17 am
by pixel
Lukasz wrote:I came up with this since _init() was just a jr ra, nop function
Can somebody tell me where this symbol comes from exactly? :)

I mean, it's like it's provided by the linker or something... it's here in the final binary, but it seems it automagically went there, since it is not here in any file in the ps2sdk. Does the linker provide it automatically if it's not here, in order to avoid missing symbols ? Who/what should provide it anyway ? Should we provide it as a crtX.o ? I've never seen _init and _fini used elsewhere than in .so files... Seems I am getting confused there, and now I have to dig stuff ;)

Posted: Wed Nov 17, 2004 3:29 am
by mrbrown
It's in crti.o (or crtbegin.o), which is in GCC (gcc/config/mips/crti.asm and gcc/crtstuff.c).

Posted: Wed Nov 17, 2004 4:33 am
by pixel
Damn me... why haven't I thought of it.

Posted: Wed Nov 17, 2004 1:17 pm
by Guest
pixel wrote:Damn me... why haven't I thought of it.
Damn you, indeed.

Posted: Mon Nov 22, 2004 4:33 pm
by stefan
Ok.

I have checked in support for C++ global/static constructors and destructors and in the process exposed a problem in how ps2sdk linked programs. First off, the change I made to crt0.s should be backwards compatible with older EE compilers if someone is still using them.

The problem was that ps2sdk used -nostartfiles in its Makefiles (Rules.make) when doing the final link of a program. This prevented crtbegin.o, crti.o, crtend.o, and crtn.o from being linked with the program. These files hold the magic of the static constructors. This is why Lukasz only saw _init() as jr $ra (I think).

Anyway, the result of me fixing that (using -mno-crt0 instead of -nostartfiles) means that normal C programs will now have .ctors and .dtors and also the _init()/_fini() magic. Note however that GCC supports static constructors and destructors in C, if you use the attributes that mrbrown mentioned (see the GCC manual). Those sections can be easily stripped from your executable, if you aren't using them (see ee-strip --help).

The only other weird thing right now is that the linker puts the destructor code before _start(). I tried a few things but couldn't fix it, but I'll get it sorted out soon enough. Please let me know if you have any problems with what I've checked in. I'd also like feedback from C++ users: get rid of the current _init()/_ctors() hack and let me know if it works for you. Thanks.

Posted: Mon Nov 22, 2004 4:52 pm
by mrbrown
Excellent work, stefan.

Note that stefan completed what MrHTFord started when he added support for _init and _fini to GCC 3.2.2. And if you don't like the extra sections, remember that in GCC 2.9EE instead of _init there was __main(), which was called at the top of main() regardless of whether or not your program was C or C++, or whether or not you had any global constructors. stefan's change only adds a few more bytes but brings the tools back into the state they were before I butchered them and MrHTFord started to fix them :P.

Now why did it take so long for someone to fix this?

Posted: Mon Nov 22, 2004 4:58 pm
by mrbrown
Sorry, one last thing: You can still specify -nostartfiles if you don't want any of the _init/_fini stuff. Just add it to EE_LDFLAGS in your Makefile.

Posted: Mon Nov 22, 2004 10:27 pm
by blackdroid
mrbrown wrote:Now why did it take so long for someone to fix this?
because you are the dark lord, and thee we fear.

Posted: Tue Nov 23, 2004 9:32 am
by ooPo
yea and verily

Posted: Tue Nov 23, 2004 10:05 am
by rasmus
The constructors and destructors are working very well for me now, thanks a lot Stefan!

The only thing I noticed is that static objects at function-level does not get destructed at all, ie

Code: Select all

void foo&#40;&#41; &#123;
        static Bar x;

&#125;
The Bar destructor is never called for x even though the constructor is called. This behaviour is differs from native gcc on Cygwin, but I suppose it doesn't matter very much. I just thought I should mention it.

Great work!

Posted: Tue Nov 23, 2004 11:26 am
by mrbrown
rasmus wrote: The only thing I noticed is that static objects at function-level does not get destructed at all, ie

Code: Select all

void foo&#40;&#41; &#123;
        static Bar x;

&#125;
The Bar destructor is never called for x even though the constructor is called. This behaviour is differs from native gcc on Cygwin, but I suppose it doesn't matter very much. I just thought I should mention it.
Correct me if I'm wrong, but because you declared x as static, then it really has module-level (or global) scope, and it won't be destructed until your program exits. Can you let your program fall off via main() or call exit() and see if that's the case?

Any C++ gurus in the house? :P

Posted: Tue Nov 23, 2004 12:38 pm
by mharris
mrbrown wrote:Correct me if I'm wrong, but because you declared x as static, then it really has module-level (or global) scope, and it won't be destructed until your program exits. Can you let your program fall off via main() or call exit() and see if that's the case?

Any C++ gurus in the house? :P
Yes, you've described what *should* happen. Although the scope of the variable is the enclosing block -- foo() in this case -- the object's lifetime is the program. The constructor can either be called the first time foo() is entered, or at program initialization (not sure if the C++ standard defines this behavior, but most implementations I've seen do it at program start).

In any event, the destructor should be called when the program completes, either by main() exiting, or by calling the exit() stdlib function (which does global destruction, as opposed to _exit(), which does not). There shouldn't be any difference in behavior between a privately-scoped static object and one with global scope.

However, in most ps2dev programs for the EE, there's a call to SleepThread() at the bottom of main() -- so main() never exits, and global dtors will not be called. This is obviously different from most other environments (i.e., ones that have an operating system to return to).

The short answer: if main() never exits, global destructors will never be called.

Posted: Tue Nov 23, 2004 2:48 pm
by radad
mharris wrote:The constructor can either be called the first time foo() is entered, or at program initialization (not sure if the C++ standard defines this behavior, but most implementations I've seen do it at program start).
I believe it is defined to be the first time the function is entered. I have even seen that behaviour used explicitly to ensure proper ordering of construction. The order of file scope static objects is undefined when they are in different compilation units. This behaviour gets around that.

Posted: Tue Nov 23, 2004 4:02 pm
by mrbrown
A note about calling exit() or returning from main() normally (without SleepThread()): At some point pukklink, and thus ps2link, was fixed up so that ps2link would handle a program falling off of main(), or one that called exit(). In the _exit() routine in crt0.s, there is cleanup code for the case where the program was executed by ps2link. This is also where stefan added code to call _fini() which will call global destructors. Because ps2sdk's libc exit() calls _exit(), global deconstructors should be called normally.

The other thing to consider is how your app will run. If you're writing a game or emulator or something along those lines, then you know you'll be running until the user hits reset. So there's never really a need to clean up behind yourself. But maybe one of these days someone will come up with a smooth way for apps to return to their parent, so that all of these ELF loading utilties that keep cropping up can execute a program and expect for it to clean up after itself before returning control to the loader.

I guess what I'm trying to say is IMO it's always a good idea to clean up after yourself. SleepThread() at the end of main() has been obsolete for some time now. :P

Posted: Tue Nov 23, 2004 8:33 pm
by rasmus
I am sorry if I was unclear. I didn't expect x to be destroyed when the function exits, but when the program exits. A global instance of Bar is destructed properly after main() with Stefan's fixes. x does never get destructed though.

Please see this test program:

Code: Select all

#ifdef _EE
#include <tamtypes.h>
#include <sifrpc.h>
#include <kernel.h>
#endif

#include <stdio.h>

class Bar &#123;
public&#58;
  Bar&#40;int x&#41; &#58; m_x&#40;x&#41; &#123;
    printf&#40;"Bar&#58;&#58;Bar&#40;%d&#41;\n", x&#41;;
  &#125;
  ~Bar&#40;&#41; &#123;
    printf&#40;"Bar&#58;&#58;~Bar&#40;%d&#41;\n", m_x&#41;;
  &#125;
  int m_x;
&#125;;

Bar x1&#40;1&#41;;

void f&#40;&#41; &#123;
  static Bar x2&#40;2&#41;;
  Bar x3&#40;3&#41;;
&#125;

int main&#40;&#41; &#123;
#ifdef _EE
  SifInitRpc&#40;0&#41;;
#endif
  f&#40;&#41;;
  return 0;
&#125;
The expected output (which I get when I compile with gcc in cygwin) is:

Code: Select all

Bar&#58;&#58;Bar&#40;1&#41;
Bar&#58;&#58;Bar&#40;2&#41;
Bar&#58;&#58;Bar&#40;3&#41;
Bar&#58;&#58;~Bar&#40;3&#41;
End of main&#40;&#41;
Bar&#58;&#58;~Bar&#40;2&#41;
Bar&#58;&#58;~Bar&#40;1&#41;
But on the PS2 the static object in the function (x2) is never destructed, even though I exit main() properly.

Code: Select all

Bar&#58;&#58;Bar&#40;1&#41;
Bar&#58;&#58;Bar&#40;2&#41;
Bar&#58;&#58;Bar&#40;3&#41;
Bar&#58;&#58;~Bar&#40;3&#41;
End of main&#40;&#41;
Bar&#58;&#58;~Bar&#40;1&#41;

Posted: Thu Nov 25, 2004 10:47 am
by mharris
Hmm, I've seen odd stuff before when trying to trace global dtors -- since it's happening after the program ends, all bets are off. I ran into trouble a while ago on some unix machine where stdout was closed before the global dtors ran (so everything was actually working OK, it just ate my debug output).

I'm not saying that's the problem -- in fact it's probably not, since you're getting Bar::~Bar(1) in the output. But be aware that weird things can happen after program's end...

Posted: Sun Jun 19, 2005 11:38 am
by cheriff
Hey c++ ppl!
I'm trying to move some cpp code onto ps2 and it does use <vector>. I link against stlport and all seems fine. Untill I try to instansiate a class (thats has all methods implemented inline in the .h, if it makes a difference):

At the global scope:
myClass thingy;
is the line that does it. removing this and it compiles and links no worries.

this one line spews forth a barrage of undefined references:setlocale, __assert, _impure_ptr, atoi, mcSync. Even sscanf, when i don't use it myself.

the link command is:

Code: Select all

ee-gcc -T/usr/local/ps2dev/ps2sdk/ee/startup/linkfile -L/usr/local/ps2dev/ps2sdk/ee/lib -L /usr/local/ps2dev/ps2sdk/ports/lib \
        -o demo.elf a.o b.o  -ldraw -lgraph -lmath3d -lmf -lpacket -ldma -lstdc++ -lc -lstlport -lm -lstlport -lstdc++ -lstlport -lc -lstlport -lstdc++ -lstdc++ -lc -lkernel
i know i've probably gone overboard with the -lstlport and -lstdc++ but am posting this after much trial & error (mostly error, but)

any ideas?

Posted: Sun Jun 19, 2005 9:21 pm
by pixel
First, consider using stlport. Second, some functions are indeed not defined, such as vsscanf, or setlocale. Just write stub functions for them, that'll do it for now...

Posted: Tue Jun 21, 2005 10:24 pm
by cheriff
Thanks, i finally got it. I wasn't linking against libg. My link command:

Code: Select all

ee-gcc -mno-crt0 -T/usr/local/ps2dev/ps2sdk/ee/startup/linkfile -L/usr/local/ps2dev/ps2sdk/ee/lib -L /usr/local/ps2dev/ps2sdk/ports/lib \
        -o demo.elf /usr/local/ps2dev/ps2sdk/ee/startup/crt0.o main.o interface_ps2.o modplayer.o modplayer_thread.o sjpcm_rpc.o vsync.o -lc -lstlport -lstdc++ -ldraw -lgraph -lmath3d -lmf -lpacket -ldma   -lstlport -lm -lstlport  -lstlport  -lstlport -lg -lmc -lc -lkernel
I've prolly got lots of redundant stuff in there, but this works and is good enough for me. Havent tested the stl stuff yet, but at least it compiles in just fine.

Also, in the makefile
include $(PS2SDK)/samples/Makefile.eeglobal_cpp causes bad startup code, when it gets to main():

Code: Select all

001006b0 <main>&#58;
  1006b0&#58;	27bdffb0 	addiu	sp,sp,-80
  1006b4&#58;	3c040014 	lui	a0,0x14
  1006b8&#58;	ffb00000 	sd	s0,0&#40;sp&#41;
CRASH
Address store exception. apparantly sp is 0x01ffffc0
but Makefile.eeglobal works fine, so no biggie there

Posted: Wed Jun 22, 2005 3:32 am
by pixel
"libg" is the debug version of newlib....... you just didn't read what I said earlier.



oh, well, after all, nevermind.

Posted: Wed Jun 22, 2005 3:57 am
by mrbrown
Actually libg is the exact same thing as libc. Some weird fruity GNU thing. I guess it's convenient when ps2sdk has a library called libc as well.

Posted: Wed Jun 22, 2005 4:27 am
by pixel
I thought it was "the-same-but-built-without-O3", but it obviously seems I was wrong. Exactly the same binaries. *shrug*