PURIFY.A Purify.a is a source file for the Aztec assembler. Its purpose is to allow you to make "pure" (reentrant and reexecutable) programs using Aztec C. To use purify.a, first assemble it with this command: as purify.a and then link the resulting object file purify.o into your program. Typically that would be done with a command something like this: ln yourprogram.o purify.o -lc You may want to place the purify.o object module in your lib directory to use many times. The linker apparently knows to look for object modules in the directory specified by the CLIB variable if they can't be found in the current directory, though I can't find documentation of this feature. The way that it works is by replacing the low-level startup code which is the first thing that runs before the beginning of the main program. It makes a copy of all the global variables and causes the rest of the program to use the copy instead of the original. The original globals are kept around only to act as a template to make the copy from. The copy is freed when the process exits. The object file purify.o must appear in the command line BEFORE the standard library does; that is, before -lc or -lc16, whichever you are using. You can't use -lcl or -lcl16, because purify.o only works with small data. (If you've ever wondered which library to use if you have large code and small data, or vice versa, the answer is to pick the library according to how you're accessing the data, not the code.) The complete list of conditions that your program must meet to become pure is as follows: 1) The program must not access its own global variables with the large data memory model. Large code is okay, and it's okay to use large data to refer to things outside of your own global variables, such as direct access to the Amiga's hardware (custom.xxxxxx, cia stuff, etc). It is also okay to use large data to refer to constants. The thing is that when you use large data, you refer to the original "template" global variables, and when you use small data, you refer to the private copy of those variables. In the case of constants, there's no difference. It may be possible to use large data to refer to variables which are always initialized to a constant value before being used, such as library bases, IF you are careful not to mix the two kinds of access. All global data, initialized and uninitialized, must be in a single segment less than 64K in size for small data to be used. I think the linker enforces this. 2) You MUST NOT call the geta4() function from within any part of your program that is run outside of your main process. Subtasks, interrupt handlers, and wedges installed with SetFunction may not use geta4, and thus may not access your global variables directly. This is because if several processes are all using the same code, as resident programs may do, there's no way for geta4 to tell which process's variables to point the a4 register to. Yes, I know this means that geta4 is essentially useless now. 3) You must not statically initialize global variables using the addresses of other global variables. For example, the following is not correct: char foo[100], bar[100]; char *foobar[2] = { foo, bar }; /* WRONG */ If you do this, then any references to foobar[0] will end up accessing DIFFERENT MEMORY than accessing foo directly will. Accessing foo or bar will refer to the private copy of those variables, but accessing foobar[0] or foobar[1] will refer to the original template variables. For the program to be reentrant and reexecutable, you would have to change the above so that foobar is initialized in the code, with statements like "foobar[0] = foo; foobar[1] = bar;", rather than initializing it statically. 4) You must not use detach.o, or any similar means of making the program automatically detach itself from its parent CLI process, without great care. No program that is made resident can use Aztec's detach.o in its present form, whether it uses this startup code or not. It may be possible to come up with a version that is usable, however. 5) Do not use this when making a shared library or device driver. ("Duh...") I recommend that you keep the size of your global variables small. Since it makes a complete copy of all global variables for each process, it can be rather wasteful if you declare a 50000 byte array in your global variables. One should use AllocMem or malloc for such things, and keep the size of your global variables down to a few K if possible, unless you just don't care about how much memory you take up. This was originally based on resident.asm by Olaf Barthel, which is available on fish disk 396, but does not presently contain any of his code. Barthel's version had serious errors in it, and will not work in many cases. It was also a lot less efficient than the method I use. To enumerate his errors: - he used "destructive testing" on SysBase->AttnFlags when checking whether a floating point unit is present, clearing all the other flags. (BOZO!) - he stashed the copied global variables on the task's tc_MemEntry list to be automatically freed when the task exits. This meant that when a program exited back to a parent shell, the copy would not be freed at that time, and would stay in memory until the shell itself was ended. - he used an AddTail when he should have used an AddHead, which in combination with the above error, made the code work ONLY ONCE in any given shell process. If you ran the same program again in the same shell, or even a different program that also used resident.asm, it would see the FIRST program's global variables instead of its own. (OOOOOPS!) How my version works is different; it hides a pointer to the copy of the global variables on the process's stack where geta4() can find it, and sets things up so that no matter how the program exits, it has to go through the cleanup routine before finishing. Note: do not try to assemble this using the Aztec assembler's -N flag (one pass), because there's a bug, at least in 5.2a and older, that makes the ENTRY directive not work with that option. It will need a few changes in syntax to work with other assemblers. It might be possible to make versions for other C compilers, so I've labeled everything which I know to be Aztec specific with a comment containing the word "Aztec". The one possibly uncool bit of programming used here is that I used SysBase->ThisTask instead of FindTask(0), in several places. If they ever make an Amiga that runs tasks on several CPUs at once, that might break. Another thing... Aztec's original startup module opened dos.library and never closed it. This one closes it. This is by Paul Kienitz, 1 December 1991, placed in the public domain.