8

I'm working on tweaking ABC-800 BASIC II machine code, and there's a routine there that does a "256-byte" equivalent of LDIR. B is not used, C=0 transfers 256 bytes, C=1 transfers 1 byte, etc.

The existing code is:

0000                             ; C - length (0=256 bytes)
0000                             ; DE - destination
0000                             ; HL - source
653D                          .ORG   653dh   
653D   ED A0        BLKTF:    LDI      
653F   AF                     XOR   A   
6540   B1                     OR   C   
6541   20 FA                  JR   NZ,BLKTF   
6543   C9                     RET      

That is nice, compact code, but obviously using LDIR could be faster. The best I could come up with is one byte longer:

653D                          .ORG   653dh   
653D   AF           BLKTF:    XOR   A  ; clear A 
653E   B9                     CP   C   ; CF=0 if C was zero
653F   3F                     CCF      ; CF=1 if C was zero
6540   17                     RLA      ; rotate C into A
6541   47                     LD   B,A ; now BC = 1..256
6542   ED B0                  LDIR      
6544   C9                     RET  

Is there a way to do better, retaining the same API?

7
  • 4
    LDIR is famously slow. It may make code clearer, quicker to write, or possibly a smaller binary, etc. I do not know if it would be slower than the code you're starting with though. Jan 6 at 6:19
  • 4
    It will not be slower original code take 16+4+4+12 ldir takes 21(rest is executed once and takes 4+4+8)... So it's slower only for c =1
    – Selvin
    Jan 6 at 6:46
  • See How fast is memcpy on the Z80?. And from this article on hacker news: On the Z80, LDIR is not as fast as unrolling the loop to produce a block of LDI instructions. You can jump into the middle of the block at count % blocksize to copy sizes which are not a round number. This was a fairly common game trick - Oh, that's actually a circular link. Jan 6 at 23:43
  • Does this answer your question? How fast is memcpy on the Z80? Jan 6 at 23:45
  • 3
    @Greenonline Not really. Those cases are about LDOR and generic mem copy. This is about a very specific case with 8 bit length and 00 equalling 256. Also, it's about code size, not/less about speed optimization.
    – Raffzahn
    Jan 7 at 1:10

1 Answer 1

12

You can go with:

653D                          .ORG   653dh   
653D   06 00                  LD    B, 0   
653F   0D                     DEC   C   
6540   03                     INC   BC   
6541   ED B0                  LDIR   
6543   C9                     RET  

or

653D                          .ORG   653dh   
653D   AF                     XOR   A   
653E   47                     LD    B, A   
653F   0D                     DEC   C   
6540   03                     INC   BC   
6541   ED B0                  LDIR   
6543   C9                     RET 

When C is 1-255 then, after decrementation, it is 0-254. After incrementation of BC, it returns to its previous value. However, if it is 0 then after DEC it becomes 255. INC BC will overflow C and increment B which was 0 as we set it at the beginning.

Final note: are you sure that you can change B?

3
  • 3
    Dang, this is awesome! Thank you so much! I assume that B could change since in the original code the C=0 case always modifies B. I'll look at the call sites to make double sure. Thankfully Ghidra makes that easy. I tried the DEC BC, INC C route - so close :) Now I know to always try it multiple ways :) Jan 6 at 15:51
  • 1
    Oh yes, I forgot that ldi can also modify bc
    – Selvin
    Jan 6 at 17:14
  • My attempt was `XOR A / LD B,A / LDI / RET PO / LD B,A / LDIR / RET`` which I think would be a little faster., since "LDI / RET PO" would take the same amount of time as one iteration of "LDIR", but only need one 4-cycle instruction as "cleanup", eliminating the need to do a six-cycle "INC BC".
    – supercat
    Jan 8 at 17:53

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .