Writing the code to validate ranges in type definitions in Quiche-Z80 I needed to test for some values. I could easily handle the cases where both values where unsigned or both values where signed but the case where one was signed and the other unsigned allowed a nice little optimisation.
Due to the typing rules of Quiche this combination, signed and unsigned, would always result in an unsigned range. Therefore the signed bound had to have a positive value – a negative one would be a compile time error. So, if the lower bound was the signed one the code needed to load and test the lower bound value. If the upper bound was the signed one the code needed to load and test the upper bound. In pseudo-code I needed something like the following:
Test if lower bound is the unsigned one
If so jump to 'lower'
Load upper bound
Test upper bound and raise error as needed
Jump to 'common_code'
lower:
Load lower bound
Test lower bound and raise error as needed
common_code:
…
First Refactor
In this code both tests use the same code so we can refactor them into the common code section:
Test if lower bound is the unsigned one
If so jump to 'lower'
Load upper bound
Jump to 'common_code'
lower:
Load lower bound
common_code:
Test bound and raise error as needed
…
Preloading
While this code is better it still leaves us with two jumps occupying memory and burning cycles. A nice little assembly coding trick for this scenario is to load one of the values. If that’s the one we need we jump to the test, if not we load the alternate value, thus reducing the code down to a single jump. In pseudo-code we get:
Test if lower bound is the unsigned one
Load lower bound
If so jump to 'common_code'
Load upper bound
common_code:
Test bound and raise error as needed
…
Note how the loading of the lower bound has moved up to between the test and the jump. If the upper bound is the required one then the load of the lower bound will have been wasted but the benefits in terms of byte-size and execution speed generally outweigh that disadvantage.
The key requirement to get this to work is that the loading of the value must not change the value of whatever flag is being testing in the jump.
In Quiche
Here is the code I ended up with for Quiche-Z80:
;So, one bound is integer and the other is pointer. The combined result must be
;pointer (because pointer is the dominant type). We need to make sure whichever
;bound is signed is a positive value.
;C=Upper.VarType
ld a,c ;A=Upper-bound.VarType
and a ;Integer or Pointer?
ld a,(accumulator+1) ;High byte of upper bound
jr z,.upper_unsigned ;If lower bound is pointer we test sign of upper bound...
;..otherwise test sign of lower bound
ld a,(left_data+1) ;High byte of lower bound
.upper_unsigned
rla ;Move high bit to carry
ld a,vtPointer ;The type of the range will be vtPointer
jr nc,_sametype_store ;If signed value was >= 0, store as pointer
jr _local_raise_errorintype ;Err if negative