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