Proper C++ support in ee-gcc?
Proper C++ support in ee-gcc?
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 :)
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 :)
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 ;)
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.
_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.
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..."
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
In main() I have :
This method isn't pretty, calling the constructors and destructors should really be done in crt0.s, it does however work :)
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)
}
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(dtor) dtor();
return 0;
}
Can somebody tell me where this symbol comes from exactly? :)Lukasz wrote:I came up with this since _init() was just a jr ra, nop function
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.
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.
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.
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?
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..."
-
- Posts: 564
- Joined: Sat Jan 17, 2004 10:22 am
- Location: Sweden
- Contact:
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
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!
The only thing I noticed is that static objects at function-level does not get destructed at all, ie
Code: Select all
void foo() {
static Bar x;
}
Great work!
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?rasmus wrote: The only thing I noticed is that static objects at function-level does not get destructed at all, ie
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.Code: Select all
void foo() { static Bar x; }
Any C++ gurus in the house? :P
"He was warned..."
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).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
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.
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.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).
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
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..."
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:
The expected output (which I get when I compile with gcc in cygwin) is:
But on the PS2 the static object in the function (x2) is never destructed, even though I exit main() properly.
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 {
public:
Bar(int x) : m_x(x) {
printf("Bar::Bar(%d)\n", x);
}
~Bar() {
printf("Bar::~Bar(%d)\n", m_x);
}
int m_x;
};
Bar x1(1);
void f() {
static Bar x2(2);
Bar x3(3);
}
int main() {
#ifdef _EE
SifInitRpc(0);
#endif
f();
return 0;
}
Code: Select all
Bar::Bar(1)
Bar::Bar(2)
Bar::Bar(3)
Bar::~Bar(3)
End of main()
Bar::~Bar(2)
Bar::~Bar(1)
Code: Select all
Bar::Bar(1)
Bar::Bar(2)
Bar::Bar(3)
Bar::~Bar(3)
End of main()
Bar::~Bar(1)
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...
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...
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:
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?
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
any ideas?
Damn, I need a decent signature!
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.
Thanks, i finally got it. I wasn't linking against libg. My link command:
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():Address store exception. apparantly sp is 0x01ffffc0
but Makefile.eeglobal works fine, so no biggie there
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
Also, in the makefile
include $(PS2SDK)/samples/Makefile.eeglobal_cpp causes bad startup code, when it gets to main():
Code: Select all
001006b0 <main>:
1006b0: 27bdffb0 addiu sp,sp,-80
1006b4: 3c040014 lui a0,0x14
1006b8: ffb00000 sd s0,0(sp)
CRASH
but Makefile.eeglobal works fine, so no biggie there
Damn, I need a decent signature!
"libg" is the debug version of newlib....... you just didn't read what I said earlier.
oh, well, after all, nevermind.
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.
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.