Re: A Macro to Generate Jump Tables
- From: randyhyde@xxxxxxxxxxxxx
- Date: 8 Jul 2005 08:59:13 -0700
Charles A. Crayne wrote:
> On 7 Jul 2005 15:32:49 -0700
> randyhyde@xxxxxxxxxxxxx wrote:
>
> :Creating a
> :true switch statement using an assembler's macro facilities is
> :relatively difficult and requires a *very* powerful macro subsystem.
>
> Although your macro is indeed powerful, and I can see some value in using
> it in an HLA context, I believe that those of us who prefer to program in
> the more traditional assembler languages will find very little of value in
> a such a macro.
Sure. But someone who prefers to program in a "more traditional
assembly language" probably sees little value in macros anyway.
> However, I will not be upset to be proven wrong, so please
> consider the following analysis, and respond as you will.
>
> To begin with, you have described your macro as "A Macro to Generate Jump
> Tables", and I am evaluating it in that context -- since one certainly does
> not need a macro to generate a "jmp [jmptbl+ebx*4]" statement.
Actually, the *first* macro I presented in this thread did that job.
The Switch/Case macro was a second one I provided that demonstrates
another way to use jump tables. For those who want to work in a more
traditional assembly langauge environment, the first macro I presented
generates the table only and leaves it up to the programmer to write
the "jmp [jmptbl+ebx*4]" statement as well as manually supply all the
code for the cases. The second macro ("switch/case/default/endswitch")
simply provides an extension of that idea to fill in and automate the
remainder of the code.
BTW, other than one extra jump at the beginning of the code sequence,
the macro generates *exactly* the same code a typical assembly
programmer would write when creating a multi-way branch (i.e., switch)
statement. So there is little to be gained by hand-coding the result.
>
> Next, you wrote "you need to allow switches within switches
> within switches within... nested to any level". Although this is true
> (albeit trivial) in the code section (for which we have already agreed that
> no macro is necessary), it is certainly false for the generated jump
> tables.
????
The comment refers strictly to the use of the switch macro. Most
attempts at a "switch" macro that I've seen fail to allow nestable
invocations. Not all, but most.
> Each jump table is, and must always be, a separate array, at the
> same program level.
Sure. But most macro implementations of a switch/case macro I've seen
break big time when you nest invocations. The problem is that stacking
up things like the default case and the "end of switch" address isn't
done properly (or the assembler can't handle it).
>
> In addition, you stated "You need the ability to store up all the case
> values in a compile-time array so you can emit the jump table *after*
> processing all the cases present in the switch/case statement." This is
> only true if one's goal is to emulate an HLL switch syntax, but is
> certainly false if one's goal is merely to generate jump tables, as is the
> usual case in assembly languages, and as your choice of subject suggests.
The purpose of the switch/case/default/endswitch macro is to emulate
the HLL switch syntax. The purpose of the *first* macro I provided in
this thread was to build the jump table for those who don't want to use
that syntax. Also note that sorting the jump table entries is *still*
necessary, even for the jump table generation (the first macro I
provided does not require the case values to be given in order), but in
the implementation I provided with the first macro, the sorting was
easily accomplished because that macro didn't have to track code labels
and such (it generated labels on its own and required the user to
provide code (procedures) using those generated labels).
>
> And finally, you argued that "You need a sophisticated enough compile-time
> language that will let you sort the array of case values (and corresponding
> destination addresses) prior to emiting the jump table (as the entries must
> be sorted by index before being output)." At first glance, the automated
> sorting of the jump table seems desirable -- since an out of order table is
> clearly disastrous. However, since the programmer must enter the switch
> value in the case statement, and since most programmers will enter the case
> statements in numerical sequence, it takes fewer keystrokes for a
> programmer to create a jump table in the correct sequence, to begin
> with, and not have to deal with case statements at all.
There are two problems with requiring the programmer to enter the
values in sorted order:
1. It is error-prone. The whole purpose of writing a macro to automate
jump table generation (first macro) or emulate a switch statement
(second macro) is to help eliminate the defects that occur when people
fail to generate jump tables correctly. Requiring someone to enter the
case values in sorted order eliminates one of the big advantages of
using the macro -- because it no longer prevents these kinds of defects
from occuring.
2. Who said the values are literal constants that the programmer can
easily sort themselves? What if the values take the form "WM_....."
and the programmer has no idea what their actual values are? (Or would
spend forever manually sorting them.) Remember, it's the job of the
computer to do this kind of grunt work; why force the programmer to
manually sort these values when the computer can do it for them?
Granted, *some* assemblers don't have the power to do this (I suspect
that RosAsm falls into this category) and users of such assemblers may
have no choice but to manually sort the data. But that's their own
problem for choosing a weaker assembler.
>
> For example, you ask the programmer to enter
>
> case( 0 )
> <some nameless routine> );
>
> which you state will generate
>
> L820__case0_0___hla_:
> <some nameless routine>
> jmp L824__0329__switch_endcase____hla_
>
> in the code section, and
>
> dd L820__case0_0___hla_
>
> in the jump table. [Incidentally, I note that this differs significantly
> from the C/C++ standard].
You may have missed the point in my discussion where I said that a
single variable controls C/C++ or Pascal semantics. By default, the
macro supports Pascal semantics (which is *much* better as a default
than the C case). However, by changing one "equate" in the macro header
file, you *can* obtain C/C++ semantics. Just another example of the
power of using a macro for this purpose -- it's very easy to change how
the macro generates code.
>
>
> In contrast, to take a real life example, my switch statement is coded
> [in Fasm] as
>
> jmp [jmptbl+eax*4]
>
> there are no case statements, and my jump table is coded as
>
> maxsect equ 16 ;largest valid section number
> jmptbl dd buildx ;0 - end of file
> dd vocab ;1 - vocabulary
> dd text ;2 - room names
> dd text ;3 - long descriptions
> dd text ;4 - short descriptions
> dd motion ;5 - movement table
> dd text ;6 - object descriptions
> dd text ;7 - miscellaneous messages
> dd osetup ;8 - init obj loc & wt & clone
> dd actdef ;9 - action defaults
> dd brmatr ;10 - room attributes
> dd text ;11 - class messages
> dd oadj ;12 - object differentiation
> dd text ;13 - administrative msgs
> dd rdmsg ;14 - read message table
> dd rdmsg ;15 - search message table
> dd ovride ;16 - default override values
>
> Of course, one still has to write the various routines listed in the jump
> table, but, in contrast to embedding them in case statements, I get to
> give them meaningful names, optimise their placement in the code section,
> and document the jump table with useful comments.
Again, look back at the *first* macro I posted to this thread. It will
generate that jump table for you.
>
> In short, for a skilled assembly language programmer, my code is easier to
> write, easier to read, easier to maintain, and is likely to provide better
> run-time performance.
Easier to write? Most people would disagree. The simple fact that you
have to maintain the jump table manually creates all kinds of pain.
Note, for example, how you've manually typed the indexes of each of the
table entries into your table. It took extra effort to write those
comments, that wouldn't be needed if you used a macro to generate the
jump table for you. Further, you had to carefully place the entries in
that table in a globally-proscribed order. That effort wouldn't have
been necessary had you used a jump table generating macro or a
switch-style statement.
Easier to read? Even if you limit your audience to *only* expert
assembly language programmers, I'd argue that most expert assembly
language programmers know languages like C and would find a switch
statement easier to read. I don't particularly believe a jump table
generating macro is a whole lot easier to read than the jump table
itself (they'd be about the same, quite frankly), but certainly the
bare jump table is not "easier to read".
Easier to maintain? Here you're well off the mark. Inserting and
removing jump table entries is a real PITA when you've hard-coded the
table by hand as you've done. If you decide to change the commands used
as indexes into the table, you've got to completely recode the table.
If you're using the jump table generation macro or the
switch/case/default/endswitch macro, all you need do is change your
constants and recompile. Also, the switch macro automatically
generates code to validate the bounds of the index to the table. You
might consider this "bloat" that slows down the code (a CMP and a Jcc),
but this is exactly the kind of bloat that makes the code *far* easier
to maintain. Should your index go out of bounds for any reason, you can
catch that problem in your code by providing a default case in the
switch statement and have that range maintained for your automatically
(this only applies to the switch macro, you're still on your own with
the jmp table construction macro).
Better run-time performance? The jmp table macro doesn't generate any
code, so the performance is not an issue. The switch macro does
generate three extra instructions (an extra JMP and a bounds check) for
your particular table. Technically, I'd argue that only one extra
instruction is generated, as any decent code sequence is going to test
the bounds on the index anyway. Of course, when we're talking about a
jump table for the commands in an adventure game, worrying about one or
three instructions is hardly an issue, is it?
There are certainly some circumstances where a manually-coded multiway
branch construct is better than using the switch macro. Though I still
argue that using a macro (not necessarily the one I provided, but a
macro nonetheless) to generate the jump table is a *very* good idea.
That's the beauty of programming in assembly language -- the programmer
gets to decide when it *really* is important to hand code that indirect
jump. For example, if you can guarantee that the index will never be
out of bounds (not a good assumption for maintainability, but...), then
having the switch macro generate the extra CMP/Jcc is a waste; if that
waste really matters, handing coding that one multi-way branch is a
good idea. OTOH, I'd argue that better than 90% of the time, the switch
macro generates the code you want. And I argue that it's more
maintainable, easier to read, and easier to write by *all* assembly
programmers, not just expert ones.
Cheers,
Randy Hyde
.
- References:
- A Macro to Generate Jump Tables
- From: randyhyde
- Re: A Macro to Generate Jump Tables
- From: randyhyde
- Re: A Macro to Generate Jump Tables
- From: Charles A. Crayne
- A Macro to Generate Jump Tables
- Prev by Date: RamDrive.sys and Win2K/XP
- Next by Date: Re: A Macro to Generate Jump Tables
- Previous by thread: Re: A Macro to Generate Jump Tables
- Next by thread: Re: A Macro to Generate Jump Tables
- Index(es):
Relevant Pages
|