85%
A Z-80 program will be 15% smaller than an 8080 program.
To come up with this guess I took a reasonably tight Z-80 program, the TRS-80 4K Level 1 BASIC ROM and estimated the cost of replacing the Z-80 specific instructions with 8080 code. I'll get back to the limits of this methodology later but for now let me plow on with this example.
Of the 4096 bytes in the ROM, 3708 comprise 2100 Z-80 instructions. And 508 of those are not available on the 8080.
16 are DJNZ
which can almost universally be replaced with DEC B; JP NZ
at a cost of 2 bytes each.
248 are relative jumps (JR) which can be replaced with absolute jumps (JP) at a cost of 1 byte each.
The remaining 244 I figured would cost either 1 or 2 extra bytes to replace. At 1 byte each the total code size would be 3708 + 16 * 2 + 244 or 4232 bytes. At 2 bytes, 3708 + 16 * 2 + 244 * 2 or 4476 bytes.
Reducing 4232 bytes to 3708 is a 12% reduction.
Reducing 4476 bytes to 3708 is a 17% reduction.
15% looks like a nice round number in between those two.
Sketching why one or two bytes
A good deal of the remaining Z-80 instructions were bit test, set and reset (the ED
prefix opcodes). The 8080 can do something like those on the A
register using AND
and OR
instructions. Here's an outline to replace them:
BIT 3,B ; 2 bytes
...
LD A,B
AND 8 ; 3 bytes
SET 7,C ; 2 bytes
...
LD A,C
OR 128
LD C,A ; 4 bytes
RES 4,H ; 2 bytes
...
LD A,H
AND $EF
LD H,A ; 4 bytes
An average of 1.7 extra bytes. Of course, the A
register may not be available for simple translation. Saving and restoring it would cost 2 more bytes and would be problematic with the BIT
instruction which sets flags. But I'll stick with the 1 to 2 byte estimate. The value may not need to be immediately loaded back into the register and often a small amount of restructuring could eliminate the need to save A
register.
Another number of instructions were under the ED
prefix. The 3 prominent ones are LD (mem),DE
, SBC HL,DE
and LDIR
.
LD (mem),DE
can be done with two extra bytes in 8080:
LD (mem),DE ; 4 bytes
...
EX DE,HL
LD (mem),HL
EX DE,HL ; 5 bytes
You may be done with DE
and HL
and not have to exchange them back. Or in this example you only need one swap with a little trickery and there is no cost:
LD HL,(mem1)
LD DE,(mem2) ; 7 bytes
...
LD HL,(mem2)
EX DE,HL
LD HL,(mem1) ; 7 bytes
SBC HL,DE
is often just used to subtract the two registers without carry (the Z-80 does not have a SUB HL,DE
instruction). Often the 3 byte sequence OR A; SBC HL,DE
is used (OR A
is a Z-80 idiom to clear the carry flag). And commonly it is used to wastefully subtract a constant. Consider:
LD DE,1000
OR A
SBC HL,DE ; 6 bytes
...
LD DE,-1000
ADD HL,DE ; 4 bytes
Being a little more clever makes the 8080 code smaller and faster than the straightforward Z-80 solution.
To literally replace SBC HL,DE
when can do:
LD A,L
SBC E
LD L,A
LD A,H
SBC D
LD H,A ; 6 bytes
That's a 4 byte loss. However if you actually wanted to subtract without the initial carry then the replacement is still 6 bytes but the original is 3 bytes. And at this point we should also consider the king of code density: subroutines. If SBC HL,DE
occurs frequently it can be replaced by a call to a subroutine that does just that at a cost of a single extra byte. Over a large program the fixed overhead of the subroutine will amortize down to nearly zero. And there's no extra cost on each instruction if the OR A; SBC HL,DE
3 byte idiom is used.
This becomes more clear for LDIR
and other block moves - a two byte instruction which can always be done with a 3 byte call.
The example program uses 37 instructions that involve the IX
register (and none for IY
). The main power of the index registers is that they can do (almost) anything HL
can do at the cost of a single extra byte. Or 2 bytes if they are used to access memory via a signed byte offset -- LD D,(IX+3)
or LD (IY-2),E
.
Here it becomes difficult to imagine directly replacing them. I will argue that in many cases HL
can take over for them at little to no cost because programs tend to access memory sequentially. For example, consider:
LD IX,mem
LD B,(IX+4)
LD C,(IX+5)
LD D,(IX+6)
LD E,(IX+7) ; 15 bytes
...
LD HL,mem+4
LD B,(HL)
INC HL
LD C,(HL)
INC HL
LD D,(HL)
INC HL
LD E,(HL) ; 10 bytes
The pure 8080 code is 5 bytes shorter. Of course, there are situations where it will be more difficult. Once again we may appeal to either programmer cleverness or subroutines to postulate a lower overhead.
The last two instructions are harder to reason about. EX AF,AF'
and EXX
switch to alternate register sets. In essence they give you an entire new bank of 8080 general purpose registers to work with. This is a boon to speed but we can once again use subroutines to reduce the overhead to a mere 2 bytes per use asymptotically.
EXX ; 1 byte
...
CALL zexx ; 3 bytes
...
zexx: (subroutine left as an exercise for the reader)
In summary, I contend that in many cases there are replacements that cost only one or two extra bytes and in other cases subroutines can be used to make the cost per use at most 2 bytes and in large programs the fixed cost of a subroutine approaches zero.
And than isn't even considering the RST
instructions which are single-byte CALL
instructions though only 8 are available. Used strategically they can reduce the overhead even more.
Reasonable Objections
Only one program was examined.
If you buy the Z-80 instruction cost analysis then this is easily remedied by studying other programs. But the only question is what percentage of the program being Z-80 only is reasonable. The rest is just math. The BASIC ROM was 25% Z-80 only instructions.
Replacing instructions with subroutine calls is really expensive.
It sure is, but we're looking an density, not speed. And that's more than plenty to grasp.
This doesn't take into account programmer cleverness.
The chances are good that a clever programmer starting from scratch will produce a smaller 8080 program. However, with very minor exceptions that will also be a valid Z-80 program so it makes the question moot.
But what we'd really like to know is how much smaller an 8080 program could be if written in Z-80.
Yes, that would be nice to know. And useful. But that seems like a much harder question involving the skill of the programmer.
Can we just game the system by writing a Z-80 interpreter in 8080 and thus claim for any Z-80 program larger than twice the size of that interpreter the density is actually worse?
I don't see why not. Again, density, not efficiency is in question. It may seem silly, but consider any program which uses some data-driven or interpreted code for its operation. How exactly do you account for that? Pedantically we can only really fall back on Z-80 and 8080 instructions that are directly executed by the processor. But in general programming it is a legitimate and fruitful technique to reduce code size. It does, however, seem like a solution out of the bounds of the spirit of the question. I think it is important to keep in mind if you are looking at actual solutions and deserves mention as a complicating factor in quantifying the density question.