Assembler relaxation

From llvm-mos
Jump to navigation Jump to search

The 65xx series of processors has multiple types of instructions that can be encoded differently, depending on the size of the target address. For example, consider:

lda hello,x

In the 6502 case, this instruction could be encoded as 0xb5, indicating that hello is an 8-bit (zero page) address. Alternately, it might be encoded as 0xbd, indicating that hello is a 16-bit address.

In order to determine which encoding to use, the assembler must either (1) calculate the final value of the hello symbol, or (2) receive a hint as to which address space that hello should exist in.

LLVM's assembler has a feature called relaxation, in which individual instructions may be replaced with larger instructions, depending on how they would need to be encoded. However, the exact encoding of an instruction depends on the value that the symbol resolves to. And the value of that symbol might not be resolved until link time. By the time that the linker is running, the relaxation step of the assembler is well over.

This chicken-and-egg problem has multiple solutions. It might be possible to create different pseudo opcodes, such as lda8 and lda16, that map directly to one specific encoding. But this solution is incompatible with all existing 6502 code. It also might be possible to modify the LLVM linker to rerun the assembly relaxation step once all memory addresses are finalized during linking. The gcc toolchain has some support for this. But as of this writing, this idea is still novel for the LLVM toolchain.

The solutions we've gone with, provide multiple ways to tell the assembler and the linker that you want to put a symbol in zero page.

A symbol will resolve to an 8-bit address, and instruction encoding will occur under that assumption, if:

  1. the value of the symbol resolves, at assembly time, to an 8-bit non-zero constant; or
  2. the symbol is previously defined in a section with one of the following names: .zp, .zeropage, and .directpage; or
  3. the symbol is defined in a section marked with the special z flag.

If none of these conditions apply, then the symbol will refer to a 16-bit address, and instruction encoding will take place under that assumption.

So, one way to force a zero page access is fairly straightforward:

low_addr = 55 + 2 * 4
lda low_addr,x

Just define the address as a constant expression, and the assembler will deduce that a zero-page opcode is required. This is the classic solution, and most 8-bit programmers will be comfortable with it. The downside to this method, is that you have to do all memory management yourself, when ELF and the linker already have the information they need to assign those 8-bit addresses for you.

Another way to force a zero page access, is to tell the assembler your intention, by placing the symbol in one of the specially named zero-page sections:

 .section zp
 low_short:
   .byte 0x00 0x00
   .section text
 high_short:
   .byte 0x01 0x00
   lda low_short,x

A third way to force a zero page access is to mark the section with the special `z` flag:

 .section .lowmemory,"z",@nobits
 adrlowmemory: .ds.b 1

Here, the assembler will understand that the adrlowmemory symbol will eventually be located in zero page. Therefore all subsequent references to it will require one byte.

This solution lets the linker figure out the exact location in zero-page memory where the low_short symbol can go. It's up to the linker script to choose a reasonable zero-page location, on a per-target basis, for symbols marked as described above. Meanwhile, the assembler gets the hint it needs to make the lda instruction reference zero page, not 16-bit memory.

If you know nothing about this feature, then by default all symbols end up in 16-bit memory. This is the safest, but most memory hungry, option. This should work fine for most applications. But for those developers that actively want to put stuff in zero page, a Google search should lead them to this page and this solution.