autc04 / Retro68

a gcc-based cross-compiler for classic 68K and PPC Macintoshes
GNU General Public License v3.0
548 stars 54 forks source link

A5 ignored on asm() register-clobber list #220

Open rhalkyard opened 9 months ago

rhalkyard commented 9 months ago

Given an asm() block such as:

void test_a5(void) {
    asm volatile(
        "clr.l %%a3\n\t"
        "clr.l %%a4\n\t"
        "clr.l %%a5\n\t"
        :
        :
        : "a3", "a4", "a5"
    );
}

Registers A3, A4, and A5 are marked as 'clobbered' and should be saved and restored. However, the generated code (from m68k-apple-macos-gcc -S a5test.c) preserves A3 and A4, but not A5:

test_a5:
        link.w %fp,#0
        move.l %a4,-(%sp)
        move.l %a3,-(%sp)
#APP
| 2 "a5test.c" 1
        clr.l %a3
        clr.l %a4
        clr.l %a5

| 0 "" 2
#NO_APP
        nop
        move.l (%sp)+,%a3
        move.l (%sp)+,%a4
        unlk %fp
        rts

I don't have a copy of mainline GCC 9 to test this against, but m68k GCC 13.2 does not exhibit this behavior, saving and restoring A5 correctly along with the others: https://godbolt.org/z/r5zese79j

I'm assuming that this is a side effect of modifying GCC to avoid using A5 due to its special nature on the Mac OS, but in this case it has the opposite effect!

rhalkyard commented 9 months ago

I should add, this is easy enough to work around by just 'manually' saving and restoring A5 in the assembly block, but it was definitely a fun issue to track down! Mostly just putting this here to document it - I wouldn't consider it a high priority to fix.

autc04 commented 9 months ago

That's an interesting... consequence.

I guess that %a5 is reserved as a fixed register, but it's not actually considered 'used', so it doesn't get saved & restored.

GCC seems to behave similarly on other platforms... on ARM, %x29 is the frame pointer, and....

int foo();

void useFramePointer()
{
   foo();
}

void clobberFramePointer()
{
    asm volatile(
        "add x29, x29, x29"
        : : : "x29"
    );
}

gives me

useFramePointer():
        stp     x29, x30, [sp, -16]!
        mov     x29, sp
        bl      foo()
        ldp     x29, x30, [sp], 16
        ret
clobberFramePointer():
        add x29, x29, x29
        ret

[see godbolt](https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,selection:(endColumn:1,endLineNumber:15,positionColumn:1,positionLineNumber:15,selectionStartColumn:1,selectionStartLineNumber:15,startColumn:1,startLineNumber:15),source:'int+foo()%3B%0A%0Avoid+useFramePointer()%0A%7B%0A+++foo()%3B%0A%7D%0A%0Avoid+clobberFramePointer()%0A%7B%0A++++asm+volatile(%0A++++++++%22add+x29,+x29,+x29%22%0A++++++++:+:+:+%22x29%22%0A++++)%3B%0A%7D%0A'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:33.333333333333336,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:arm64g1320,filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,libs:!(),options:'-O',overrides:!((name:edition,value:'2021')),selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+ARM64+gcc+13.2.0+(Editor+%231)',t:'0')),k:33.333333333333336,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compilerName:'rustc+1.73.0',editorid:1,fontScale:14,fontUsePx:'0',j:1,wrap:'1'),l:'5',n:'0',o:'Output+of+ARM64+gcc+13.2.0+(Compiler+%231)',t:'0')),k:33.33333333333333,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4)

... in other words - it's carefully saving and restoring %x29 before it does a function call, but it does nothing to protect it from the asm statement.

So let's leave this ticket here as a warning until there's a piece of documentation that mentions it...