When generating code for arm64, why do gcc + clang use adrp+add rather than adr for addressing nearby variables?
14:12 10 Mar 2019

With arm64, a literal for a nearby address can be loaded into a register with the adr instruction. According to the ARM-V8 Architecture Reference Manual the adr instruction:

ADR , 

can reference labels within +/-1MB. There is a page version with bit-31 set, adrp, for constructing larger offsets.

What I don't understand is why neither gcc 8.2 nor clang 7.0 for ARM64 use adr rather than an adrp and add pair for nearby variables. Optimization levels don't change this.

int write(int fd, const void *buf, int count);

void xyz(void)
{
    write(2, "abc", 4);
}

xyz(): // @xyz()
  adrp x1, .L.str
  add x1, x1, :lo12:.L.str
  orr w0, wzr, #0x2
  orr w2, wzr, #0x4
  b write(int, void const*, int)
.L.str:
  .asciz "abc"

Can they not reason that this string literal is within +/-1MB? Is there a compiler attribute/switch to tell them this?

gcc clang arm64