NEWS, EDITORIALS, REFERENCE
Code Module Exports Table
In an earlier post, Organizing A Big Project, I discussed how one can use a double jumptable to gain access to the inner offsets of a module of code. I've since discovered a much more sensible way to solve this problem. It's a variant of the same concept.
To recap, the problem is that a big project is more than can be handled by a single file of assembly code. The code has to be broken down into modules of code that can be independently assembled. This leads to certain difficulties however. JMPs from the jump table that exist in one module, "memory.o" in C64 OS, have to know where to jump to. Typically, one uses a label to mark the position in some code. Then a JMP can specify the label, and the actual address of the label and where the JMP has to go get resolved at assemble time.
A problem arises when the code is assembled in different modules. How can the jump table in one module know exactly where it ought to jump to in a completely different module of code that is assembled independently? Matters are made worse because the code inside the module could be rearranged at any time. Well, we know how to handle the situation of code being rearranged, you use a jump table! So my initial solution was as follows: Put a jump table at the very top of every module of code. Thus, their offsets start at 0 and skip 3 bytes for each jump. Then, in the main system jump table, the only thing that needs to be known is where the module starts in memory. The main jump table can then jump to the module's jump table, which jumps to the right code.
The theory is perfect, in practice it also works incredibly well. But, as it turns out there is a way to do it that is somewhat smarter. I just didn't know about it because I've only been doing real programming in 6502 assembly since last October.
In my fairly recent post on pointers I was reading about the memory addressing modes of the various instructions, and discovered that JMP has two variant op-codes. The first is the one we know and love, jumps to an absolute address. The second variant takes an inline pointer. And not just to zero page, but an inline pointer to anywhere in memory. Reads the address from the pointer and jumps to that address. This is exactly what we need.
Now, instead of having a full jump table at the top of each module, there is a table of pointers. And as it turns out, these are very easy to build in Turbo Macro Pro. Here's how a table of pointers looks at the top of a module.
The .word keyword reserves two bytes, in little endian format. And it can get that address from a label. The series of .word declarations are a table of pointers to the labels further down in this file. Note that each pointer is only two bytes, whereas the old jump table required three bytes, an extra byte for the extra JMP instruction. Note also that the exports are the first code that appears in this file, so the pointer to initmouse is at exactly $c7c4.
In order for other modules to be able to use the code in this module, they include the header for this module. The example I'm using is the input module. Which contains the code for keyboard and mouse scanning and event generation. Including the code for reading and dequeueing each of the event types.
Not much has changed here. inpuths is set to the address within the system jump table where the jumps to the exports exist. Note that these labels increment by three bytes at a time. That is because they are referring to the main jump table, which includes the JMP instructions. The last part is the system jump table itself and its offsets.
The label input has to be declared the same as the start of where the module is actually assembled to. In this case $c7c4 as we saw earlier. The offset of these entries into the jump table is there in the comments just for reference and to help updating the header file. Each JMP now uses the notation for indirect addressing, those are the parentheses around the address.
Note that these offsets increment by 2. That's because they're offsets into the table of export pointers in the module. The comments specify the routine this indirect jump is jumping to, just for clarity.
Let's review the savings. Before, two direct jumps were required. A direct jump takes 3 cycles for a total of 6 cycles per call through the main jump table. An indirect jump takes 5 cycles, so one cycle is saved per call. Before, two direct jumps required 6 bytes of memory. An indirect jump requires 5, so one byte saved per call. Not a huge saving, but everything counts when you have only 64K of ram. The other small advantage is that counting by twos in the main jump table feels easier to do than counting by threes. Also, over all the .word table at the top of a module just feels cleaner.
Here's the code in screenshots.