gccassembly68hc11

Getting GCC to optimize hand assembly


In an attempt to make GCC not generate a load-modify-store operation every time I do |= or &=, I have defined the following macros:

#define bset(base, offset, mask) bmanip(set, base, offset, mask)

#define bclr(base, offset, mask) bmanip(clr, base, offset, mask)

#define bmanip(op, base, offset, mask) \
asm("pshx");\
asm("ldx " #base);\
asm("b" #op " " #offset ",x " #mask);\
asm("pulx")

And they work great; the disassembled binary is perfect.

The problem comes when I use more than one in sequence:

inline void spi_init()
{
  bset(_io_ports, M6811_DDRD, 0x38);
  bset(_io_ports, M6811_PORTD, 0x20);
  bset(_io_ports, M6811_SPCR, (M6811_SPE | M6811_DWOM | M6811_MSTR));
}

This results in:

00002227 <spi_init>:
    2227:       3c              pshx
    2228:       fe 10 00        ldx     0x1000 <_io_ports>
    222b:       1c 09 38        bset    0x9,x, #0x38
    222e:       38              pulx
    222f:       3c              pshx
    2230:       fe 10 00        ldx     0x1000 <_io_ports>
    2233:       1c 08 20        bset    0x8,x, #0x20
    2236:       38              pulx
    2237:       3c              pshx
    2238:       fe 10 00        ldx     0x1000 <_io_ports>
    223b:       1c 28 70        bset    0x28,x, #0x70
    223e:       38              pulx
    223f:       39              rts

Is there any way to get GCC (3.3.6-m68hc1x-20060122) to automatically optimize out the redundant stack operations?


Solution

  • gcc will always emit the assembly instructions you tell it to emit. So instead of explicitly writing code to load registers with the value you want to manipulate, you instead want to tell gcc to do this on your behalf. You can do this with register constraints.

    Unfortunately the 6811 code generator doesn't seem to be a standard part of gcc --- I don't spot the documentation in the manual. So I can't point you at platform-specific bit of the docs. But the generic bit you need to read is here: http://gcc.gnu.org/onlinedocs/gcc-4.8.1/gcc/Extended-Asm.html#Extended-Asm

    The syntax is freaky, but the summary is:

    asm("instructions" : outputs : inputs);
    

    ...where inputs and outputs are lists of constraints, which tell gcc what value to put where. The classic example is:

    asm("fsinx %1,%0" : "=f" (result) : "f" (angle));
    

    f indicates that the named value needs to go into a floating point register; = indicates it's an output; then the names of the registers are substituted into the instruction.

    So, you'll probably want something like this:

    asm("b" #op " " #offset ",%0 " #mask : "=Z" (i) : "0" (i));
    

    ...where i is a variable containing the value you want to modify. Z you'll need to look up in the 6811 gcc docs --- it's a constraint which represents a register which is valid for the asm instruction which is being generated. The 0 indicates that the input shares a register with output 0, and is used for read/write values.

    Because you've told gcc what register you want i to be, it can integrate this knowledge into its register allocator and find the least-cost way to get i where you need it with the least amount of code. (Sometimes no additional code.)

    gcc inline assembly is deeply contorted and weird, but pretty powerful. It's worth spending some time to thoroughly understand the constraint system to get the best use out of it.

    (Incidentally, I don't know 6811 code, but have you forgotten to put the result of the op somewhere? I'd expect to see an stx to match the ldx.)

    Update: Oh, I see what bset is doing now --- it's writing the result back to a memory location, right? That's still doable but it's a bit more painful. You need to tell gcc that you're modifying that memory location, so that it knows not to rely on any cached value. You'll need to have an output parameter with constraint m which represents that location. Check the docs.