Sunday, October 28, 2012

HL macros technical

A more technical explanation of the HL macros I created, so others will feel more comfortable using them, as well as for myself in the event I forget how they work.

I'll be focusing mainly on the if macro due to the fact it was first, and the other macros are very similar.

To begin, somewhere in code the ca65 runs into the macro 'if'. From beginning to end, this is what the macro logic does:

1. Pass the entire parameter token list to the macro. There is only one parameter, commas will cause problems unless the parameter is enclosed in {}. (Example: Passing an indexed mnemonic)

2. Check for goto label on the end of the parameter. If there is a 'goto', strip it and the label from the parameter string and call the main if_macro with the label as the last macro parameter.
(The syntax goto can be changed to user preferred syntax - look for the line:
 ".define _USER_GOTO_DEFINITION goto")

3. In this macro, check if the first character is a bracket. If so, find the matching closing bracket. If the closing bracket is on the outside, strip the outside brackets.

4. Scan from left to right for the 1st matching OR symbol (user defined, default: || ). If found, split the parameters into everything to the left of the OR and everything right of the OR. Everything to the left is recursively expanded with recursive and leftsideOR set, everything to the right is recursively expanded with recursive (or possibly lastrecursive if no more OR/AND found) set. Setting leftsideOR signals that expansion that there are more branch(es) to follow and the logic to combine them is OR.

Setting recursive signals to the macro expansion that it should not end the branches (there is more to come)  and that it should not increment the current if block counter. If a goto label was set it is always passed in the recursive expansions.

5. If no more OR, test for AND. This works similarly to the OR, but sets leftsideAND, and results in logic that combines the following branch instruction with AND logic.

The macro code has no ability to properly handle more than one logical test to the left of an AND or OR due to how this works. It may be possible to make it work, but it would result in a much more complex macro and not nearly as efficient 6502 code in my opinion. As well OR logic must be first to make things easier for the macro and the 6502.

6. Scan for any more ANDs or ORs on the right of the last AND/OR. If none found, the last recursive call sets lastrecursive, which is only to signal to the macro to not increment the if counter. Everything else happens as if it was a regular simple if statement.

During any of this, regular brackets are honored and will only be scanned inside as described in step 3. This allows for placing an OR after an AND, ie if cond1 && (cond2 || cond3) will work properly, as well as recognized symbols to be passed to custom macro 'functions'.

After this step, it would be similar to coding all these if macros, line by line with the different parameters set to combine the logic. The tokens in between the OR and AND (the conditions to 'evaluate') symbols are passed to another macro expansion that checks for a valid flag to be tested and turned into a branch instruction. If it does not find a flag in the form of foo bar, where foo is the flag (C, Z, N, V, G) and bar is the default (set, clear) it will assume that it should be including the code from a macro, or some inline instructions:

It will attempt to create code from anything up to a + symbol. If this is an instruction or macro, it will be output, scan for another instruction or macro and output it until an equality ( == ) is found, or, if no equality symbol, a compatible macro sets the flags with set_flag_test 'command'.

Code explanation

I've been testing this a lot with Super Mario Brothers, and rewriting some of the disassembly for testing and to hopefully make it more readable, which results in some good examples:


;Original:

  lda OperMode           ;are we in victory mode?
  cmp #VictoryModeValue  ;if so, go ahead
  beq ChkPauseTimer
  cmp #GameModeValue     ;are we in game mode?
  bne ExitPause          ;if not, leave
  lda OperMode_Task      ;are we running game engine?
  cmp #$03
  bne ExitPause          ;if not, leave
  ChkPauseTimer:
   ;code block
ExitPause:

;HL if macro:

if (comp OperMode = #VictoryModeValue) || ((comp a = #GameModeValue)  && (comp OperMode_Task = #$03))                                                               
; code block
endif

This does produce the same code, so a closer look of how:

The first test is a macro that is called and expanded resulting in the assembly:

lda OperMode
cmp #VictoryModeValue
set_flag_test Z set

After that the Z set test is interpreted to the correct branch instruction due to the leftsideOR setting in the recursive macro call. A branch to the beginning of the code to be executed is created.

beq IF_CODE_BLOCK_START_LABEL_0001

So if this test is true, no other branches would need to be checked.
The next comparison macro expanded:


cmp #GameModeValue
set_flag_test Z set
This time, however, leftsideAND is set and because this test is now required to be true to continue, the flag is negated and a bne instruction to endif  is generated.


bne _END_IF_0001

If the test passed then there is one more, and this (because it is last) is treated like a regular if without any recursive restrictions except that the if counter is not incremented. (This also results in the creation of the  IF_CODE_BLOCK_START_LABEL_ ) This test is also required to pass to enter the code block, so the opposite condition is branched on:

; the macro is expanded:
lda OperMode_Task
cmp #$03
set_flag_test Z set

; the branch is negated:
bne _END_IF_0001


The complete code output would look like this:
  lda OperMode
  cmp #VictoryModeValue
  beq IF_CODE_BLOCK_START_LABEL_0001
  cmp #GameModeValue
  bne _END_IF_0001
  lda OperMode_Task     
  cmp #$03
  bne _END_IF_0001
  IF_CODE_BLOCK_START_LABEL_0001:

   ;code block

_END_IF_0001:
Of course, you never see this code, but this is what the macro is creating. I feel some low-level coders are afraid that higher levels constructs take away their control and perhaps produce poor code, so I also hope to show that this is not a true concern if you understand how the macro code is working, and how to avoid those pitfalls and make coding easier. The other looping macros are very similar.

If goto is used, the macro will behave very differently and not use any counters, and invert the test. Normally the test will jump to the label (_ENDIF_IF_) if it fails, but with goto the code will jump to the user defined label if it passes.

The macro will also create efficient long jumps (jmp) if set_long_branch + is set.

 
set_long_branch +

; If start not pressed, and start and A not pressed then:  
  if ( comp a <> #Start_Button )  && ( comp  a <> #( A_Button+Start_Button))  
 
  set_long_branch -

This results in:
  cmp #Start_Button
  beq _IF_ENDIF_JMP_
  cmp #A_Button+Start_Button
  bne IF_CODE_BLOCK_START_LABEL_0001

_IF_ENDIF_JMP_:    
  jmp _END_IF_0001             
IF_CODE_BLOCK_START_LABEL_0001:
Which is the same as Super Mario Brothers code:

  cmp #Start_Button
  beq StartGame
  cmp #A_Button+Start_Button  ;check to see if A + start pressed
  bne ChkSelect               ;if not, check select
StartGame:    
  jmp ChkContinue             ;if start or A + start, execute
ChkSelect:    
Update: As well I'd like to demonstrate what is possible with the inline code as well: 
Using the previous example:
if (comp OperMode = #VictoryModeValue) || ((comp a = #GameModeValue)  && (comp OperMode_Task = #$03))     
This can also be written as:
if (lda OperMode + cmp #VictoryModeValue == equal) || ((cmp #GameModeValue == equal)  && (lda OperMode_Task + cmp #$03 ==  equal))     
If you don't feel like writing a macro or you want more control over what is happening in a specific case.

For now that's it, I don't know if I am going to write up too many more macros like this, possibly a switch statement if I find I need it, or possibly an elseif to use with the if-endif.

No comments:

Post a Comment