I finished converting everything to 64-bit. It was way easier than I expected and I’m very glad I took the time to do it because what I thought were vast and insurmountable differences turned out to be pretty minuscule. I managed to tuck everything away in macros, the ABI differences in prolog
and epilog
, and enter
(née function_entry_point
) and call
, and the register-size differences in much simpler ones like load
and store
which just map to lwz
/ld
and stw
/std
respectively. Hiding the ugly stuff in the assembler keeps the generators happily free of conditionals.
My next job is designing the interpreter calling convention. There’s no real reason for the interpreter to follow the platform’s ABI, and the fact that Java is essentially stack-based and PPC is essentially register-based is a very good reason not to. So I’m trying to figure out how to arrange the stack.
The general layout of stack frames is the same under both 32- and 64-bit ABIs:
| ... | high addresses
+-> | Link area |
| +----------------------+
| | Register save area |
| | Local variable space |
| | Parameter list space |
+---+ Link area | low addresses
+----------------------+
The stack grows downwards, and the stack pointer, r1
, points to the first word of the link area, the lowest address, such that all accesses into the stack are relative to r1
with a positive offset. The ABIs are pretty relaxed about what happens in the stack, but one thing they’re firm about is that 0(r1)
points to the previous frame — essentially it’s where you save your caller’s r1
. This is slightly irritating, because I think the interpreter would like an open-ended stack, but the requirement to maintain a valid link area at the very top of the stack would seem to preclude this. Aside from anything else, if r1
isn’t pointing to a valid link area then gdb cannot unwind the stack and produce backtraces. I discovered this empirically a while back ;)
My thinking at the moment is to leave r1
alone, and to use another register (r31
maybe) as the interpreter’s stack pointer. That way the interpreter can extend the stack however it likes, without thought to link areas and alignment, on the assumption that if it ever jumps out into C-land then it must first create a valid stack frame around it’s own data to protect it. Specifically, this will be a frame with no register save area, meaning the interpreter’s stuff falls neatly into the local variable space.
I’m not sure how stack walking will work under this scenario. It may be that it’s better to do this stack-shuffling every time the interpreter calls a new method, such that each method call, be it Java or C, has it’s own valid ABI stack frame. This will undoubtedly resolve itself as I progress.