ASM Diaries 4: Don’t Optimise Error Paths

This piece should really be called ‘optimise the path which is not the error path’ but that’s a very unsexy title. The point here is that your code’s error path is (usually) executed a single time before returning to the user for input. That says that it’s not time-sensitive and you don’t need to optimise it for speed. (optimising for bytesize will still be relevant though).

The thing I’m getting to here is the method you use to raise the error. Suppose your subroutine either returns or raises an error. That is to say that if there’s an error the routine doesn’t return. Code like this next listing does the job nicely.

  cp '?' ;test something
  jp z,raise_error
  ret

But is this optimal? If our code doesn’t raise an error – the normal path – three instructions get executed: the compare, the jump (not taken) and the return. If there’s an error two instructions get executed: the compare and the jump (taken). What if we swap the order of the jump and the return?

  cp '?'
  ret nz
  jp raise_error

In this code the normal path executes two instructions: the compare and the return. The error path is the one which executes all three.

Not Just Returns

I see this pattern most commonly, as above, with returns but it also works with jumps – basically wherever the code path does not flow straight on. So, if the ‘normal’ path jumps to common code, or even a tail-call to a subroutine which can be reduced to a jump. The following example tests if a character is printable. If so it tail-calls the routine to print it. If not it raises an error.

  cp #1f            ;Do we have a printable character? (SPACE = #20).
  jp c,print_char   ;Print the character
  jp raise_notprintablechar

Footnotes