From 82eb7309b79dc5070947788a8bc7f6dab3693ea5 Mon Sep 17 00:00:00 2001 From: Lukas Lalinsky Date: Wed, 12 Nov 2025 23:10:20 +0100 Subject: [PATCH] [AArch64] Fix handling of x29/x30 in inline assembly clobbers The AArch64 backend was silently ignoring inline assembly clobbers when numeric register names (x29, x30) were used instead of their architectural aliases (fp, lr). I found this bug via inline assembly in Zig, which not normalize the register names the way clang does. There is an incoplete workaround for this in Rust, but that only handles `x30/lr`, not `x29/fp`. I thought it would make sense to fix this properly rather than adding a workaround to Zig. This patch adds explicit handling in getRegForInlineAsmConstraint() to map both numeric and alias forms to the correct physical registers, following the same pattern used by the RISC-V backend. I've left `x31/sp` without changes, it would nice to have to have warning when trying to clobber `x31`, just like there is for `sp`, but that register needs different handling, so it's best done separately. If you have code like this: define void @clobber_x30() nounwind { tail call void asm sideeffect "nop", "~{x30}"() ret void } Here is the generated assembly before: clobber_x30: // @clobber_x30 //APP nop //NO_APP ret And after: clobber_x30: // @clobber_x30 str x30, [sp, #-16]! // 8-byte Folded Spill //APP nop //NO_APP ldr x30, [sp], #16 // 8-byte Folded Reload ret --- .../Target/AArch64/AArch64ISelLowering.cpp | 15 ++++++++ .../AArch64/inline-asm-clobber-x29-x30.ll | 36 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 llvm/test/CodeGen/AArch64/inline-asm-clobber-x29-x30.ll diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp index 81c87ace76e56..57311e06f5387 100644 --- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp +++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp @@ -30,6 +30,7 @@ #include "llvm/ADT/SmallVectorExtras.h" #include "llvm/ADT/Statistic.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Twine.h" #include "llvm/Analysis/LoopInfo.h" #include "llvm/Analysis/MemoryLocation.h" @@ -13157,6 +13158,20 @@ AArch64TargetLowering::getRegForInlineAsmConstraint( return std::make_pair(unsigned(AArch64::ZT0), &AArch64::ZTRRegClass); } + // Clang will correctly decode the usage of register name aliases into their + // official names. However, other frontends like `rustc` do not. This allows + // users of these frontends to use the ABI names for registers in LLVM-style + // register constraints. + // + // x31->sp is not included here because it's not a general register and + // needs different handling + unsigned XRegFromAlias = StringSwitch(Constraint.lower()) + .Cases({"{x29}", "{fp}"}, AArch64::FP) + .Cases({"{x30}", "{lr}"}, AArch64::LR) + .Default(AArch64::NoRegister); + if (XRegFromAlias != AArch64::NoRegister) + return std::make_pair(XRegFromAlias, &AArch64::GPR64RegClass); + // Use the default implementation in TargetLowering to convert the register // constraint into a member of a register class. std::pair Res; diff --git a/llvm/test/CodeGen/AArch64/inline-asm-clobber-x29-x30.ll b/llvm/test/CodeGen/AArch64/inline-asm-clobber-x29-x30.ll new file mode 100644 index 0000000000000..587a85367b5ba --- /dev/null +++ b/llvm/test/CodeGen/AArch64/inline-asm-clobber-x29-x30.ll @@ -0,0 +1,36 @@ +; RUN: llc -mtriple=aarch64 -verify-machineinstrs < %s | FileCheck %s + +; Test that both numeric register names (x29, x30) and their architectural +; aliases (fp, lr) work correctly as clobbers in inline assembly. + +define void @clobber_x29() nounwind { +; CHECK-LABEL: clobber_x29: +; CHECK: str x29, [sp +; CHECK: ldr x29, [sp + tail call void asm sideeffect "", "~{x29}"() + ret void +} + +define void @clobber_fp() nounwind { +; CHECK-LABEL: clobber_fp: +; CHECK: str x29, [sp +; CHECK: ldr x29, [sp + tail call void asm sideeffect "", "~{fp}"() + ret void +} + +define void @clobber_x30() nounwind { +; CHECK-LABEL: clobber_x30: +; CHECK: str x30, [sp +; CHECK: ldr x30, [sp + tail call void asm sideeffect "", "~{x30}"() + ret void +} + +define void @clobber_lr() nounwind { +; CHECK-LABEL: clobber_lr: +; CHECK: str x30, [sp +; CHECK: ldr x30, [sp + tail call void asm sideeffect "", "~{lr}"() + ret void +}