llvm / llvm/llvm-project

AArch64 -O0: x8 used as vtable dispatch scratch register, conflicting with sret (indirect result) convention, causing SIGSEGV

Open
#190,362 4 comments 0 reactions 0 assignees View on GitHub
ABI backend:AArch64 incomplete
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Summary

When compiling C++ for `aarch64-linux-gnu` at `-O0`, the compiler generates `blr x8` for virtual function dispatch. On AArch64, **x8 is the "indirect result
location register"** — the caller must place the return-value buffer address there before calling any function returning a large struct (>16 bytes). When x8 is
overwritten with the vtable function pointer and `blr x8` is executed, the callee saves x8 (= function address, inside read-only `.text`) as its return buffer
and crashes writing the result there.

The bug **only appears at `-O0`**. At `-O2` and above the register allocator correctly uses x9 for the function pointer and leaves x8 for the return buffer.

> **Note:** This was observed using Zig 0.12.0, which bundles LLVM 17.0.6. We have not yet confirmed whether stock Clang 17 reproduces the same behavior.

## Environment

| Field | Value |
|---|---|
| LLVM version | 17.0.6 (via Zig 0.12.0) |
| Target | `aarch64-linux-gnu` |
| Host | `x86_64-linux` (cross-compilation) |
| Optimization | `-O0` (default / fastbuild) |

## Generated code

For a virtual call to a function returning `std::string` (24 bytes, requires sret), `-O0` generates:

```asm
ldr x8, [x0] ; x8 = vtable pointer from object
ldr x8, [x8, #32] ; x8 = function pointer from vtable slot 4 <- x8 clobbered!
; BUG: x8 should be the sret return buffer address here, but it is not
blr x8 ; WRONG: x8 = function pointer, not return buffer
```

The callee (`collate::do_transform`) then executes:

```asm
mov x19, x8 ; save x8 as sret buffer -- but x8 = function pointer!
stp x20, x0, [x19, #8] ; write string to x19 = code address -> SIGSEGV
```

### Correct code at `-O2`

```asm
ldr x8, [x0] ; vtable pointer
ldr x9, [x8, #32] ; function pointer into x9, not x8
add x8, sp, #24 ; x8 = sret return buffer on stack
blr x9 ; correct: x9 = function ptr, x8 = return buffer
```

## Minimal Reproduction

```cpp
// test.cpp
#include
#include

void call(const std::locale& loc, const char* lo, const char* hi) {
std::use_facet>(loc).transform(lo, hi);
}
```

```bash
# At -O0 -- shows the bug
clang++ -target aarch64-linux-gnu -O0 -fpic -c test.cpp -o test.o
aarch64-linux-gnu-objdump -d test.o | grep -B2 "blr.*x8"
# Output:
# ldr x8, [x8, #32] <- function pointer clobbers x8
# blr x8

# At -O2 -- correct
clang++ -target aarch64-linux-gnu -O2 -fpic -c test.cpp -o test.o
aarch64-linux-gnu-objdump -d test.o | grep -B2 "blr.*x[0-9]"
# Output:
# ldr x9, [x8, #32] <- function pointer into x9
# blr x9
```

## Crash Backtrace

```
#0 std::__1::basic_string::__set_long_pointer(char*)
this=0x341e98c ::do_transform(...)>
#4 std::__1::collate::do_transform(lo, hi)
#5 0xffff... <- caller that made the virtual call via blr x8
```

Register state at crash: `x19 = 0x341e98c` = address of `do_transform` itself (inside read-only `.text`)

## AArch64 ABI Reference

Per the [AArch64 Procedure Call Standard (AAPCS64)](https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst):

> **x8** — Indirect result location register. Used to pass the address of the memory location where the caller has allocated space for the indirect result.

When the return type requires indirect return (size > 16 bytes), the caller **must** set x8 to the return buffer address before the call. Using x8 as a scratch
register for the function pointer and calling `blr x8` violates this.

## Expected Behavior

The compiler should not use x8 as a scratch register for `blr x8` when the called function requires sret. Registers x9–x15 should be used for the function
pointer, and x8 should be set to the return buffer address before the call.

## Workaround

Compile at `-O2` or higher. The flag `-ffixed-x8` would also fix this but is rejected for `aarch64-linux-gnu`.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.