3

I'm trying to port some Linux C code to an Apple M1 Mac and have been encountering an issue with some inline assembly. And it has me stumped.

I have the following inline assembly block:

#define TEST asm volatile(\    
    "adr x0, label9 \n"\                                                                                                                                                          
    : : : "x0");

And have encountered the following error:

test.c:73:5: error: unknown AArch64 fixup kind!
    TEST
    ^
./ARM_branch_macros.h:2862:7: note: expanded from macro 'TEST'
      "adr x0, label9 \n"\
      ^
<inline asm>:1:2: note: instantiated into assembly here
        adr x0, label9 
        ^
1 error generated.
make: *** [indirect_branch_latency.o] Error 1

I am using the following compiler:

Apple clang version 12.0.0 (clang-1200.0.32.27)
Target: arm64-apple-darwin20.1.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

With the command line:

clang -c -o test.o test.c -I. -w -g -lrt -O0 -static -DARM_ASSEMBLY

Any help would be greatly appreciated!

Tex4066
  • 337
  • 4
  • 15

1 Answers1

8

The ADR instruction stores the offset from the current PC value to the label you reference.

When you have an instruction that references a symbol in a different object file (or in a different section in the same object file), the assembler can't encode the exact offset directly as it doesn't know how the linker will lay them out, but has to leave a relocation in the object file, instructing the linker to fix up the instruction once the exact location of the symbol is known.

I think the issue here is simply that the MachO object file (which is used on apple platforms) format doesn't have a relocation type for fixing up an ADR instruction pointing at a symbol elsewhere. And even if it had that, the construct is pretty brittle - the symbol that it points at has to be within +/- 1 MB from the instruction referencing it - that's a limit that is pretty easy to hit.

To get access to a bigger range, an ADRP+ADD instruction pair is often used, which gives you a +/- 4 GB range, and the MachO format does support those.

The assembler syntax for them differs a bit between MachO and ELF (and COFF). For MachO, the syntax looks like this:

adrp x0, symbol@PAGE
add x0, x0, symbol@PAGEOFF

Or if you want to load from it at the same time:

adrp x0, symbol@PAGE
ldr x1, [x0, symbol@PAGEOFF]

On ELF (the object file format used on Linux) and COFF (windows, when assembling GNU style assembly with LLVM) platforms, the syntax looks like this:

adrp x0, symbol
add x0, x0, :lo12:symbol
mstorsjo
  • 12,983
  • 2
  • 39
  • 62
  • 2
    As an addendum: clang supports "Linker Optimization Hints", which do make it possible to turn e.g. an `adrp`+`add` into just an `adr`+`nop` at link-time, if the relative offset permits it. This is done by giving both instructions a label and then including the assembly directive `.loh AdrpAdd L1, L2`. Information on this feature seems sparse, but [the clang source](https://github.com/llvm-mirror/llvm/blob/master/lib/Target/AArch64/AArch64CollectLOH.cpp#L14-L77) has some helpful comments. – Siguza Dec 19 '20 at 11:09