Re: [PATCH v2 1/1] aarch64: mingw: Auto-import implementation

Martin Storsjö <[email protected]> Fri, 31 Jul 2026 23:48:59 +0300 (EEST)
Newsgroups gmane.comp.gnu.binutils
Message-ID <[email protected]>
On Fri, 31 Jul 2026, Evgeny Karpov wrote:

> Auto-import is needed to resolve symbols during dynamic linking.
>
> For example,
>
>  adrp	x0, dyn_v + 2
>  add	x0, x0, :lo12:dyn_v + 2
>
> or
>
>  adrp  x0, dyn_v + 8
>  ldr   x0, [x0, :lo12:dyn_v + 8]
>
> or
>
>  b init_dyn_v
>  bl init_dyn_v
>
> init_dyn_v and dyn_v are undefined at compile time and are getting resolved
> during linking with a dynamic library to the IAT (import address table).
>
> In case of b or bl, it can be done by using a jump table

I think the term "jump table" usually refers to something entirely 
different? What you have here normally is just called a thunk.

> init_dyn_v:
>  adrp	x16, __imp_init_dyn_v
>  add	x16, x16, :lo12:__imp_init_dyn_v
>  ldr	x16, [x16]

These two instructions can be just one single "ldr x16, [x16, 
:lo12:__imp_init_dyn_v]"

>  br	x16

This seems reasonable - but the wording makes it sound odd. This 
instruction sequence (with the add+ldr merged) is a totally normal import 
thunk. For the normal (non-auto-import) case, the import library contains 
such thunks already - and for this case, if you happen to have a b/bl 
instruction referencing symbol <foo>, when <foo> is missing but 
__imp_<foo> exists, then generating this seems reasonable.

FWIW, we don't handle this case in LLD, and haven't really encountered a 
need for this case so far. If referencing something that really is a 
function, then the import library really should expose that as a function 
type (with the import thunk in the import library).

That said, doing this seems reasonable if you happen to have cases where 
it's needed.

> "adrp", "add" and "ldr" are handled differently. The codegen usually
> creates a reference to a symbol without an offset:
> adrp x0, <symbol>
> add x0, x0, :lo12:<symbol> / ldr x0, [x0, :lo12:<symbol>]
>
> however it might be necessary to support offsets in some cases:
>  adrp  x0, <symbol> +/- offset
>  add   x0, x0, :lo12:<symbol> +/- offset / ldr   x0, [x0, :lo12:<symbol> +/- offset]
>
> It is handled by detecting relocations that have symbols that are resolved
> during dynamic linking.
> "adrp" is replaced by an unconditional branching to a place where a symbol address
> will be loaded from the IAT and then returning back.
> Relocation for the "add" and "ldr" instructions is not needed in this case.
>
>  adrp	x0, dyn_v + 8
>  add	x0, x0, :lo12:dyn_v + 8  /  ldr   x0, [x0, :lo12:dyn_v + 8]
>
> turns into
>
> __fu1_dyn_v:
>  b     __imp_dyn_v_10_8
>  add	x0, x0, 8  /  ldr   x0, [x0, 8]
>  ...
>
> __imp_dyn_v_10_8:
>   adrp	x0, __imp_dyn_v
>   ldr	x0, [x0, :lo12:__imp_dyn_v]
>   add	x0, x0, offset_4k, lsl #12
>   b	__fu1_dyn_v + 0x4

Thanks for the clear examples of what this does!

I think this may work - although on first look, it feels like it's adding 
a fair amount of overhead.

On the other hand - the overhead it adds is only paid for the cases where 
the autoimported data symbol actually is referenced; code that doesn't 
autoimport any data symbols won't have any extra overhead at all.


For reference - what we did in LLVM for this was to mirror what already 
was being done for x86_64: On x86_64, GCC (and also LLVM) generates 
indirection through a .refptr stub - which essentially works like a GOT 
entry. Whenever the compiler references a symbol which it can't be sure is 
in the same module, instead of doing

     adrp x0, symbol+offset
     add x0, x0, :lo12:symbol+offset

it generates

     adrp x0 .refptr.symbol
     ldr x0, [x0, :lo12:.refptr.symbol]

     .section .rdata$.refptr.symbol,"dr",discard,.refptr.symbol
     .globl .refptr.symbol
.refptr.symbol:
     .xword symbol

(For cases where the intended code was adrp+ldr to begin with, it becomes 
one extra ldr.)

The generated code is mostly efficient, although there can be one extra 
ldr per access, and you get the binary size overhead of all the extra 
.refptr elements for all the symbols that weren't autoimported.

Given the preexisting case of GCC doing this for x86_64, I would have 
expected that the least surprising way of dealing with it would be to do 
the same for aarch64.

But anyway, I see the pros and cons of your approach, as it adds zero 
overhead for the non-autoimported cases.

I can't vouch for it, but I think the design may be ok. (I haven't 
reviewed the code so I can't speak for the actual implementation.)

// Martin