Proper C++ support in ee-gcc?

Discuss the development of software, tools, libraries and anything else that helps make ps2dev happen.

Moderators: cheriff, Herben

Post Reply
stefan
Posts: 10
Joined: Sun Nov 14, 2004 4:54 pm

Proper C++ support in ee-gcc?

Post 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 :)
pixel
Posts: 791
Joined: Fri Jan 30, 2004 11:43 pm

Post 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 ;)
pixel: A mischievous magical spirit associated with screen displays. The computer industry has frequently borrowed from mythology. Witness the sprites in computer graphics, the demons in artificial intelligence and the trolls in the marketing department.
mrbrown
Site Admin
Posts: 1537
Joined: Sat Jan 17, 2004 11:24 am

Post 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.
"He was warned..."
User avatar
Lukasz
Posts: 248
Joined: Mon Jan 19, 2004 8:37 pm
Location: Denmark
Contact:

Post 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 :)
pixel
Posts: 791
Joined: Fri Jan 30, 2004 11:43 pm

Post 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 ;)
pixel: A mischievous magical spirit associated with screen displays. The computer industry has frequently borrowed from mythology. Witness the sprites in computer graphics, the demons in artificial intelligence and the trolls in the marketing department.
mrbrown
Site Admin
Posts: 1537
Joined: Sat Jan 17, 2004 11:24 am

Post by mrbrown »

It's in crti.o (or crtbegin.o), which is in GCC (gcc/config/mips/crti.asm and gcc/crtstuff.c).
"He was warned..."
pixel
Posts: 791
Joined: Fri Jan 30, 2004 11:43 pm

Post by pixel »

Damn me... why haven't I thought of it.
pixel: A mischievous magical spirit associated with screen displays. The computer industry has frequently borrowed from mythology. Witness the sprites in computer graphics, the demons in artificial intelligence and the trolls in the marketing department.
Guest

Post by Guest »

pixel wrote:Damn me... why haven't I thought of it.
Damn you, indeed.
stefan
Posts: 10
Joined: Sun Nov 14, 2004 4:54 pm

Post 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.
mrbrown
Site Admin
Posts: 1537
Joined: Sat Jan 17, 2004 11:24 am

Post 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?
"He was warned..."
mrbrown
Site Admin
Posts: 1537
Joined: Sat Jan 17, 2004 11:24 am

Post 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.
"He was warned..."
blackdroid
Posts: 564
Joined: Sat Jan 17, 2004 10:22 am
Location: Sweden
Contact:

Post 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.
Kung VU
ooPo
Site Admin
Posts: 2023
Joined: Sat Jan 17, 2004 9:56 am
Location: Canada
Contact:

Post by ooPo »

yea and verily
rasmus
Posts: 17
Joined: Wed Jul 21, 2004 9:30 am
Location: Göteborg, Sweden

Post 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!
mrbrown
Site Admin
Posts: 1537
Joined: Sat Jan 17, 2004 11:24 am

Post 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
"He was warned..."
mharris
Posts: 155
Joined: Sun Jan 25, 2004 2:26 pm
Location: Annapolis, MD, USA

Post 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.
radad
Posts: 246
Joined: Wed May 19, 2004 4:54 pm
Location: Melbourne, Australia

Post 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.
mrbrown
Site Admin
Posts: 1537
Joined: Sat Jan 17, 2004 11:24 am

Post 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
"He was warned..."
rasmus
Posts: 17
Joined: Wed Jul 21, 2004 9:30 am
Location: Göteborg, Sweden

Post 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;
mharris
Posts: 155
Joined: Sun Jan 25, 2004 2:26 pm
Location: Annapolis, MD, USA

Post 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...
cheriff
Regular
Posts: 258
Joined: Wed Jun 23, 2004 5:35 pm
Location: Sydney.au

Post 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?
Damn, I need a decent signature!
pixel
Posts: 791
Joined: Fri Jan 30, 2004 11:43 pm

Post 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...
pixel: A mischievous magical spirit associated with screen displays. The computer industry has frequently borrowed from mythology. Witness the sprites in computer graphics, the demons in artificial intelligence and the trolls in the marketing department.
cheriff
Regular
Posts: 258
Joined: Wed Jun 23, 2004 5:35 pm
Location: Sydney.au

Post 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
Damn, I need a decent signature!
pixel
Posts: 791
Joined: Fri Jan 30, 2004 11:43 pm

Post by pixel »

"libg" is the debug version of newlib....... you just didn't read what I said earlier.



oh, well, after all, nevermind.
pixel: A mischievous magical spirit associated with screen displays. The computer industry has frequently borrowed from mythology. Witness the sprites in computer graphics, the demons in artificial intelligence and the trolls in the marketing department.
mrbrown
Site Admin
Posts: 1537
Joined: Sat Jan 17, 2004 11:24 am

Post 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.
pixel
Posts: 791
Joined: Fri Jan 30, 2004 11:43 pm

Post by pixel »

I thought it was "the-same-but-built-without-O3", but it obviously seems I was wrong. Exactly the same binaries. *shrug*
pixel: A mischievous magical spirit associated with screen displays. The computer industry has frequently borrowed from mythology. Witness the sprites in computer graphics, the demons in artificial intelligence and the trolls in the marketing department.
Post Reply