-
Notifications
You must be signed in to change notification settings - Fork 54
Global Variables and Relocation (68K)
For applications, you don't need to worry about any of this.
For code resources ('INIT'
, 'WDEF'
, ...), things are more complicated.
Before you do anything in a code resource, you need to invoke RETRO68_RELOCATE
:
#include "Retro68Runtime.h"
void _start()
{
RETRO68_RELOCATE();
// ...
}
For applications, this happens automatically before main
is invoked.
Old documentation also refers to things like an A4 worlds and A5 worlds. A4 worlds don't exist and are irrelevant with Retro68. A5 worlds can become relevant if you need to use QuickDraw from a code resource.
A4 and A5 are both address registers in the 68K processor. A global variable will reside somewhere in memory. The trouble is, your program cannot know in advance where it is in memory.
The 68K processor has several "addressing modes", i.e. ways in which a machine language instruction can refer to memory.
- 32-bit absolute address
- program counter relative, with 16-bit displacement
- address register relative, with 16-bit displacement
- others, which aren't relevant here.
The first one means that the address (of a global variable, for example), is just there as part of the instruction:
MOVE.L $00123456, D0 # read a Long word from address 0x123456 into the data register D0
# for C programmers: D0 = *(long*) 0x123456;
This is what gcc/Retro68 uses. But as we can't know in advance where the global variables will be, there has to be an additional step before the program runs where all these addresses in the code are corrected (relocation) - for INITs, you're calling that by hand, for applications, it's automatic.
Next, addresses can be specified relative to the current instruction, which is very useful, but unfortunately, the instructions only have room for a 16-bit relative distance, i.e. plus or minus 32 kilobytes. Programs often had more than 32K of code even back then, so this is not a general solution.
And third, if we manage to get the address of a memory block containing our global variables into an address register, such as A5
, we can use that to find our global variables:
MOVE.L -42(A5), D0 # read a Long word from the address that is 42 bytes below A5 into D0
# for C programmers: D0 = *(long*) (A5 - 42);
Again, we're limited to 32KB (in either direction); this limitation became slightly annoying around 1990 or so, but it was still a lot better than what was going on under MS-DOS.
When a classic Mac application is started, MacOS makes A5 point to the end of the memory block reserved for global variables. Compilers (GCC included) make sure to never use A5 for anything else, so its value is preserved all the time. The memory areas surrounding A5 is called the "A5 world."
Code running in a code resource like an INIT can't (easily) set up its own A5 world, because everyone else expects A5 to remain unchanged all the time. So many classic Mac compilers just sacrificed another register, A4, to serve the same purpose for global variables in code resources, but that had to be set up manually with help from the developer.
Retro68 never uses A4 for that purpose, so you don't need to worry about A4, and it pretty much ignores A5, so you don't need to worry about that, either.
Unless...
QuickDraw uses A5 to find its global variables. As in, the current value of A5, and as that never changes while one application is running (MultiFinder/System 7 handle things just right when switching between applications), you never need to worry about this in applications.
If you call a QuickDraw routine from a code resource, it really depends on where your code is called from. If you are called from regular application code (say, it's a desk accessory, a WDEF, or something like that), then you get the current application's A5, and you're fine.
If not, you might be in trouble and have to fiddle with SetA5
and SetCurrentA5
to get things working. (More details on request)