[AArch64] Scaled PAGEOFFSET_12L relocation emitted against COFF weak external symbols can reference under-aligned definitions
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
I got build error for my aarch64-windows-gnu toolchain for the library and here is the summary. Please merge it asap because it breaks Windows on ARM for such as simple case
Summary
=======
When an `extern_weak` symbol is explicitly marked `dso_local` in IR, the
AArch64 backend lowers GOT-style references to it into
`adrp + ldr x8, [x8, :lo12:sym]`, which emits
`IMAGE_REL_ARM64_PAGEOFFSET_12L`. That relocation type applies to *scaled*
loads and requires the final resolution of the symbol to be aligned to the
access size. A COFF weak external can be resolved by the linker to a
definition from any object file -- including a weak-external ".default"
fallback synthesized by the assembler into an arbitrary, possibly
under-aligned section -- so the alignment assumption does not hold and
lld-link correctly rejects the link with:
ld.lld: error: misaligned ldr/str offset
This makes otherwise valid code unlinkable. The problem shows up in
practice with ThinLTO: LTO's symbol resolution marks prevailing weak
symbols as dso_local (llvm/lib/LTO/LTO.cpp calls GV->setDSOLocal(true)),
after which the backend takes this path.
Reduced C++ example
===================
The idiom is a library that exports entry points as GNU-style weak
symbols on MinGW/PE targets:
```c++
// a.cpp - defines the API as weak dllexport symbols
struct S { void *a; void *b; };
extern "C" __attribute__((dllexport, weak)) const struct S *foo(void) {
static const struct S s = {0, 0};
return &s;
}
extern "C" __attribute__((dllexport, weak)) const struct S *bar(void) {
static const struct S s = {0, 0};
return &s;
}
// b.cpp - another TU of the same library refers to them
struct S;
extern "C" __attribute__((dllexport, weak)) const struct S *foo(void);
extern "C" __attribute__((dllexport, weak)) const struct S *bar(void);
__attribute__((dllexport)) int use(void) {
return foo() != bar();
}
```
After ThinLTO promotion (weak definitions are prevailing within the link,
so LTO marks them dso_local), the extern_weak declarations in b.cpp carry
the explicit `dso_local` flag:
declare extern_weak dso_local ptr @foo()
declare extern_weak dso_local ptr @bar()
Minimal reproducer
==================
```llvm
; reduced.ll
target triple = "aarch64-windows-gnu"
declare extern_weak dso_local ptr @foo()
define dso_local ptr @use() {
entry:
%r = call ptr @foo()
ret ptr %r
}
```
```console
$ llc -mtriple=aarch64-windows-gnu < reduced.ll -filetype=obj -o bad.obj
$ llvm-readobj -r bad.obj | grep 12L
0xC IMAGE_REL_ARM64_PAGEOFFSET_12L foo
```
The emitted code is:
```asm
adrp x8, foo
ldr x8, [x8, :lo12:foo] ; PAGEOFFSET_12L against a weak external
blr x8
```
Per the PE/COFF specification PAGEOFFSET_12L applies to scaled loads, so
the linker is entitled to require &foo % 8 == 0 here. Nothing guarantees
that for a weak external: its resolution may come from any TU or from an
assembler-synthesized ".default" fallback placed in an under-aligned
section (observed in .rdata with 1-byte alignment in real projects).
Root cause
==========
- TargetMachine::shouldAssumeDSOLocal() checks `GV->isDSOLocal()` before
its `hasExternalWeakLinkage()` exclusion, so an explicitly dso_local
extern_weak returns true.
- ClassifyGlobalReference() therefore skips the MO_GOT|MO_COFFSTUB branch
and falls through to the small-code-model extern_weak case, returning
bare MO_GOT (no pointer slot).
- AArch64ExpandPseudoInsts.cpp expands the LOADgot pseudo unconditionally
into ADRP + scaled LDRXui, producing PAGEOFFSET_12L directly against
the weak external symbol.
Note that references which go through a pointer slot are fine: the
MO_COFFSTUB path (.refptr.) and the MO_DLLIMPORT path (__imp_)
reference locally-emitted/import-table cells with guaranteed 8-byte
alignment. Only the bare-symbol form is affected.
Proposed fix
============
In AArch64ExpandPseudoInsts.cpp, expand LOADgot into
```asm
adrp x8, sym
add x8, x8, :lo12:sym ; IMAGE_REL_ARM64_PAGEOFFSET_12A
ldr x8, [x8] ; no relocation
```
when the operand is a global with external weak linkage on a COFF target
and carries neither MO_COFFSTUB nor MO_DLLIMPORT. REL21/PAGEOFFSET_12A
carry no alignment requirement, so the sequence is valid regardless of
where the weak external resolves.
ELF is unaffected: undefined weak callees lower to a direct BL there, and
GOT entries themselves guarantee alignment where they are used.
A regression test is available (llvm/test/CodeGen/AArch64/
coff-loadgot-weak-extern.ll); the existing windows-extern-weak.ll test
continues to pass since the .refptr. path keeps its current form.
Contributor guide
Research direction
Read AArch64ExpandPseudoInsts.cpp and the issue's proposed regression test, llvm/test/CodeGen/AArch64/coff-loadgot-weak-extern.ll. First run the reproducer through llc for aarch64-windows-gnu and inspect the emitted relocations; compare with windows-extern-weak.ll. Done means the weak COFF case avoids a scaled relocation against the weak symbol while the existing .refptr. case retains its behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100