NEWS, EDITORIALS, REFERENCE
We're back. This is a quick post to discuss a cool little programming technique I came up with recently. It's a "Switch" Macro. Let's just hop right in.
If you've ever written code in a high level language you're familiar with the usual flow control statements. You have if, else and else if statements, a few types of loop, for, while and do while, and a few others.1 One of the things that tripped me up when starting to learn 6502 is how to replicate some of these fairly mundane flow mechanisms.
For example, in an if-statement you can use logical operators to chain numerous checks that together determine if the code inside that block should be executed or not. But how do you do that sort of thing in 6502 when you only have branch operations on single conditions? Well, you have to do it very manually. When conditions are ANDed together you check each condition one at a time, and if any one of them is not true you use it to branch past the whole block. And you do the opposite for ORing conditions. You check a condition, and if it's true you branch past the condition checking and straight into the block of code. If you make it past all the condition checking and nothing branched you into the code, then you have a default branch at the bottom that skips the block of code.
Here's an example of 3 conditions ANDed together:
And here's an example of 3 conditions ORed together:
Note how there is a bit of a shortcut on the last branch of the OR conditions. Because it's the last one being checked, if it doesn't pass you can branch to skip, otherwise flow will fall through to the code.
Combining ANDing and ORing gets more complicated still. It can often lead to lots of code, and that can make it hard to visualize exactly what the code is trying to do.
Okay, now let's take another example of a flow control statement in higher level languages. The switch statment. Switch statements can really clean up your code if you have a series of blocks of code that are called depending on the value that a single variable is set to. It's much cleaner than having a whole series of if statements.
Here's a comparison of how those two look in C. First as if/else-if statements:
And then as the much improved switch statement:
In the end it just looks a lot cleaner. You can imagine that in 6502 that complicated multi-if statement looks pretty messy.
I only started to even think about it because I'm very nearly finished implementing the menu system in C64 OS. I'll be posting about that in detail soon. Just some redraw optimizations to do, and a couple of minor tweaks, and they'll pretty much be done.
The way the menus work though, is that each entry is assigned a single byte action code. Whether the entry is selected using the mouse or by using the equivalent keyboard shortcut, the action code is delivered to the application's main routine for handling menu commands. One routine is called, the accumulator holds a single character which is used to branch to a variety of routines, usually one per action code to handle that command. It is the perfect switch statement scenario, and it's going to be a big ugly pain in the butt at the center of every application's implementation if we don't have a better solution.
Here's roughly how it would look in 6502 asm.
If you have too much code between "a" and "f" then you may instead have to jmp into a separate routine somewhere. It's long and the values are spreadout, so it's hard to follow the logic, but it also uses a lot of labels. It's just nasty.
Instead of doing all that, we can use the switch macro. Here's how that looks:
I don't know about you, but to my eyes that looks a lot cleaner. First of all, the overall structure is signaled by the presence of the "#switch" macro notation. The macro takes one argument which is the number of "cases", in this example 8. Immediately following the macro call is a text constant that lists all of the possible switch values. The length of this string has to match the number passed into the macro. So in this example, we have a .text constant with a string that has 8 characters. Immediately following the .text string, we have a table of pointers to the 8 routines.
The order of the characters in the .text have to match the order of the pointers in the table. Immediately following the table of pointers is where you put the default implementation. This is equivalent to the default case of a switch statement. It's the routine that will execute only if the value is not found in the list of case characters.
This technique has many advantages: The list of all possible cases is explicit and grouped together. The table of routine pointers are grouped together, so it's very easy to see that they line up, and that you've handled everything you need to. Further, there are no labels used to tie the case values to their routine pointer. Each routine itself consists of a label, but it is easy to rearrange the case characters and then rearrange the pointers in the table to match.
The actual implementation of those routines is decoupled from the order in which they are put into the switch structure. So, you can rearrange and move about the routines themselves without worrying about the fact that the switch statement references them. Some routines which may be accessed by the menu system may equally well be called by other controls in the UI.
In fact, it gets even better than that. In some cases the routine to be called might be a system routine, in which case the application doesn't need any code at all to forward that request. Just a letter in the list of action codes, and a pointer to the system call in the table of pointers. An example of this popped up in the sample code I was using to test this, which we'll see below.
Here's how the switch macro is implemented:
The codetable label appears as the last line of the macro. That references the start of the list of case characters in the .text that immediately follows the macro. And \1 is the argument passed in that is the number of case characters.
The first loop uses .X to step through the codetable looking for a code that matches the value in .A. If it fails to find a match, execution branches to codetable plus three-times-the-case-count. That's because for each case, there is the case character plus its two-byte pointer for a total of three bytes. Three times the case count after codetable is where the default routine is found.
If the case character is found, though, .X, the index to the case character, is multiplied by two and then used as an index into the table of pointers. The table of pointers starts at codetable plus case count. The pointer to the routine is self-modded into the jmp, just before the jmp is executed. And that's it.
The switch macro's code does take up a bit of space, 34 bytes, but the greater the number of switch cases you have the less overhead the macro has. Additionally, those 34 bytes are in application memory space, not the C64 OS system code. So if you're super tight on space, or you don't want to disrupt the .X register then just don't use it. But if you can afford to use it, doing so makes the code much cleaner.
Here's what my sample code ended up looking like:
It's pretty great. You can easily see that 11 cases are available. It's easy to count the case characters and the routine pointers to see how they correspond. And the table of pointers can be organized to visually group related routines together. The first two routines, killmouse and hidemouse, are actually system calls from the input module. The main system jumptable entries are just written directly into the table of pointers and no additional code needs to be written to have the "k" and "h" action codes generated by the menus trigger those system calls. It's pretty great!