Skip to content

[ARM] Lower unaligned loads/stores to aeabi functions. - #172672

Merged
simpal01 merged 5 commits into
llvm:mainfrom
simpal01:lower_unaligned_aeabi_helpers
Feb 2, 2026
Merged

simpal01 merged 5 commits into
llvm:mainfrom
simpal01:lower_unaligned_aeabi_helpers

Conversation

@simpal01

@simpal01 simpal01 commented Dec 17, 2025

Copy link
Copy Markdown
Contributor

When targeting architectures that do not support unaligned memory accesses or when explictly pass -mno-unaligned-access, it requires the compiler to expand each unaligned load/store into an inline sequences. For 32-bit operations this typically involves:

1. 4× LDRB (or 2× LDRH),
2. multiple shift/or instructions

These sequences are emitted at every unaligned access site, and therefore contribute significant code size in workloads that touch packed or misaligned structures.

When compiling with -Oz and in combination with -mno-unaligned-access, this patch lowers unaligned 32 bit and 64 bit loads and stores to below AEABI heper calls:

         __aeabi_uread4
	__aeabi_uread8
	__aeabi_uwrite4
	__aeabi_uwrite8

And it provide a way to perform unaligned memory accesses on targets that do not support them, such as ARMv6-M or when compiling with -mno-unaligned-access. Although each use introduces a function call making it less straightforward than using raw loads and stores the call itself is often much smaller than the compiler emitted sequence of multiple ldrb/strb operations. As a result, these helpers can greatly reduce code-size providing they are invoked more than once across a program.

  1. Functions become smaller in AEABI mode once they contain more than a few unaligned accesses.
  2. The total image .text size becomes smaller whenever multiple functions call the same helpers.

This PR is derived from https://reviews.llvm.org/D57595, with some minor changes.
Co-authored-by: David Green

When targeting architectures that do not support unaligned
memory accesses or when explictly pass -mno-unaligned-access,
it requires the compiler to expand each unaligned load/store
into an inline sequences. For 32-bit operations this
typically involves:

	1. 4× LDRB (or 2× LDRH),
	2. multiple shift/or instructions

These sequences are emitted at every unaligned access site, and
therefore contribute significant code size in workloads that touch
packed or misaligned structures.

When compiling with -Os or -Oz in combination with
-mno-unaligned-access, this patch lowers unaligned 32 bit and 64 bit
loads and stores to below AEABI heper calls:
        aeabi_uread4
	aeabi_uread8
	aeabi_uwrite4
	aeabi_uwrite8

And it provide a way to perform unaligned memory accesses on
targets that do not support them, such as ARMv6-M or when
compiling with -mno-unaligned-access. Although each use
introduces a function call making it less straightforward
than using raw loads and stores the call itself is often
much smaller than the compiler emitted sequence of multiple
ldrb/strb operations. As a result, these helpers can greatly
reduce code-size providing they are invoked more than
once across a program.

1. Functions become smaller in AEABI mode once they contain more
   than a few unaligned accesses.
2. The total image .text size becomes smaller whenever multiple
   functions call the same helpers.

This PR is derived from https://reviews.llvm.org/D57595, with additional changes.
Co-authored-by: David Green
@llvmbot

llvmbot commented Dec 17, 2025

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-llvm-ir

@llvm/pr-subscribers-backend-arm

Author: Simi Pallipurath (simpal01)

Changes

When targeting architectures that do not support unaligned memory accesses or when explictly pass -mno-unaligned-access, it requires the compiler to expand each unaligned load/store into an inline sequences. For 32-bit operations this typically involves:

1. 4× LDRB (or 2× LDRH),
2. multiple shift/or instructions

These sequences are emitted at every unaligned access site, and therefore contribute significant code size in workloads that touch packed or misaligned structures.

When compiling with -Os or -Oz in combination with -mno-unaligned-access, this patch lowers unaligned 32 bit and 64 bit loads and stores to below AEABI heper calls:
aeabi_uread4
aeabi_uread8
aeabi_uwrite4
aeabi_uwrite8

And it provide a way to perform unaligned memory accesses on targets that do not support them, such as ARMv6-M or when compiling with -mno-unaligned-access. Although each use introduces a function call making it less straightforward than using raw loads and stores the call itself is often much smaller than the compiler emitted sequence of multiple ldrb/strb operations. As a result, these helpers can greatly reduce code-size providing they are invoked more than once across a program.

  1. Functions become smaller in AEABI mode once they contain more than a few unaligned accesses.
  2. The total image .text size becomes smaller whenever multiple functions call the same helpers.

This PR is derived from https://reviews.llvm.org/D57595, with some minor changes.
Co-authored-by: David Green


Patch is 33.02 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/172672.diff

4 Files Affected:

  • (modified) llvm/lib/Target/ARM/ARMISelLowering.cpp (+174-7)
  • (modified) llvm/lib/Target/ARM/ARMISelLowering.h (+5-1)
  • (modified) llvm/test/CodeGen/ARM/i64_volatile_load_store.ll (+59-63)
  • (added) llvm/test/CodeGen/ARM/unaligned_load_store_aeabi.ll (+425)
diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp
index f28640ce7b107..f9d1c8f451f4c 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp
@@ -993,6 +993,14 @@ ARMTargetLowering::ARMTargetLowering(const TargetMachine &TM_,
     setIndexedStoreAction(ISD::POST_INC, MVT::i32,  Legal);
   }
 
+  // Custom loads/stores to possible use __aeabi_uread/write*
+  if (Subtarget->isTargetAEABI() && !Subtarget->allowsUnalignedMem()) {
+    setOperationAction(ISD::STORE, MVT::i32, Custom);
+    setOperationAction(ISD::STORE, MVT::i64, Custom);
+    setOperationAction(ISD::LOAD, MVT::i32, Custom);
+    setOperationAction(ISD::LOAD, MVT::i64, Custom);
+  }
+
   setOperationAction(ISD::SADDO, MVT::i32, Custom);
   setOperationAction(ISD::UADDO, MVT::i32, Custom);
   setOperationAction(ISD::SSUBO, MVT::i32, Custom);
@@ -10012,6 +10020,130 @@ void ARMTargetLowering::ExpandDIV_Windows(
   Results.push_back(DAG.getNode(ISD::BUILD_PAIR, dl, MVT::i64, Lower, Upper));
 }
 
+std::pair<SDValue, SDValue>
+ARMTargetLowering::LowerAEABIUnalignedLoad(SDValue Op,
+                                           SelectionDAG &DAG) const {
+  // If we have an unaligned load from a i32 or i64 that would normally be
+  // split into separate ldrb's, we can use the __aeabi_uread4/__aeabi_uread8
+  // functions instead.
+  LoadSDNode *LD = cast<LoadSDNode>(Op.getNode());
+  EVT MemVT = LD->getMemoryVT();
+  if (MemVT != MVT::i32 && MemVT != MVT::i64)
+    return std::make_pair(SDValue(), SDValue());
+
+  const auto &MF = DAG.getMachineFunction();
+  unsigned AS = LD->getAddressSpace();
+  Align Alignment = LD->getAlign();
+  const DataLayout &DL = DAG.getDataLayout();
+  bool AllowsUnaligned = Subtarget->allowsUnalignedMem();
+
+  const char *LibcallName = nullptr;
+  if ((MF.getFunction().hasMinSize() || MF.getFunction().hasOptSize()) &&
+      !AllowsUnaligned) {
+    if (MemVT == MVT::i32 && Alignment <= llvm::Align(2))
+      LibcallName = "__aeabi_uread4";
+    else if (MemVT == MVT::i64 && Alignment <= llvm::Align(2))
+      LibcallName = "__aeabi_uread8";
+  }
+
+  if (LibcallName) {
+    LLVM_DEBUG(dbgs() << "Expanding unsupported unaligned load to "
+                      << LibcallName << "\n");
+    CallingConv::ID CC = CallingConv::ARM_AAPCS;
+    SDValue Callee = DAG.getExternalSymbol(LibcallName, getPointerTy(DL));
+    TargetLowering::ArgListTy Args;
+    TargetLowering::ArgListEntry Entry(
+        LD->getBasePtr(),
+        LD->getBasePtr().getValueType().getTypeForEVT(*DAG.getContext()));
+    SDLoc dl(Op);
+
+    Args.push_back(Entry);
+
+    Type *RetTy = MemVT.getTypeForEVT(*DAG.getContext());
+    TargetLowering::CallLoweringInfo CLI(DAG);
+    CLI.setDebugLoc(dl)
+        .setChain(LD->getChain())
+        .setCallee(CC, RetTy, Callee, std::move(Args));
+    auto Pair = LowerCallTo(CLI);
+
+    // If necessary, extend the node to 64bit
+    if (LD->getExtensionType() != ISD::NON_EXTLOAD) {
+      unsigned ExtType = LD->getExtensionType() == ISD::SEXTLOAD
+                             ? ISD::SIGN_EXTEND
+                             : ISD::ZERO_EXTEND;
+      SDValue EN = DAG.getNode(ExtType, dl, LD->getValueType(0), Pair.first);
+      Pair.first = EN;
+    }
+    return Pair;
+  }
+
+  // Default expand to individual loads
+  if (!allowsMemoryAccess(*DAG.getContext(), DL, MemVT, AS, Alignment))
+    return expandUnalignedLoad(LD, DAG);
+  return std::make_pair(SDValue(), SDValue());
+}
+
+SDValue ARMTargetLowering::LowerAEABIUnalignedStore(SDValue Op,
+                                                    SelectionDAG &DAG) const {
+  // If we have an unaligned store to a i32 or i64 that would normally be
+  // split into separate ldrb's, we can use the __aeabi_uwrite4/__aeabi_uwrite8
+  // functions instead.
+  StoreSDNode *ST = cast<StoreSDNode>(Op.getNode());
+  EVT MemVT = ST->getMemoryVT();
+  if (MemVT != MVT::i32 && MemVT != MVT::i64)
+    return SDValue();
+
+  const auto &MF = DAG.getMachineFunction();
+  unsigned AS = ST->getAddressSpace();
+  Align Alignment = ST->getAlign();
+  const DataLayout &DL = DAG.getDataLayout();
+  bool AllowsUnaligned = Subtarget->allowsUnalignedMem();
+
+  const char *LibcallName = nullptr;
+  if ((MF.getFunction().hasMinSize() || MF.getFunction().hasOptSize()) &&
+      !AllowsUnaligned) {
+    if (MemVT == MVT::i32 && Alignment <= llvm::Align(2))
+      LibcallName = "__aeabi_uwrite4";
+    else if (MemVT == MVT::i64 && Alignment <= llvm::Align(2))
+      LibcallName = "__aeabi_uwrite8";
+  }
+
+  if (LibcallName) {
+    LLVM_DEBUG(dbgs() << "Expanding unsupported unaligned store to "
+                      << LibcallName << "\n");
+    CallingConv::ID CC = CallingConv::ARM_AAPCS;
+    SDValue Callee = DAG.getExternalSymbol(LibcallName, getPointerTy(DL));
+    TargetLowering::ArgListTy Args;
+    SDLoc dl(Op);
+
+    // If necessary, trunc the value to 32bit
+    SDValue StoreVal = ST->getOperand(1);
+    if (ST->isTruncatingStore())
+      StoreVal = DAG.getNode(ISD::TRUNCATE, dl, MemVT, ST->getOperand(1));
+
+    TargetLowering::ArgListEntry Entry(
+        StoreVal, StoreVal.getValueType().getTypeForEVT(*DAG.getContext()));
+    Args.push_back(Entry);
+
+    Entry.Node = ST->getBasePtr();
+    Entry.Ty = ST->getBasePtr().getValueType().getTypeForEVT(*DAG.getContext());
+    Args.push_back(Entry);
+
+    Type *RetTy = Type::getVoidTy(*DAG.getContext());
+    TargetLowering::CallLoweringInfo CLI(DAG);
+    CLI.setDebugLoc(dl)
+        .setChain(ST->getChain())
+        .setCallee(CC, RetTy, Callee, std::move(Args));
+    std::pair<SDValue, SDValue> CallResult = LowerCallTo(CLI);
+    return CallResult.second;
+  }
+
+  // Default expand to individual stores
+  if (!allowsMemoryAccess(*DAG.getContext(), DL, MemVT, AS, Alignment))
+    return expandUnalignedStore(ST, DAG);
+  return SDValue();
+}
+
 static SDValue LowerPredicateLoad(SDValue Op, SelectionDAG &DAG) {
   LoadSDNode *LD = cast<LoadSDNode>(Op.getNode());
   EVT MemVT = LD->getMemoryVT();
@@ -10054,11 +10186,11 @@ void ARMTargetLowering::LowerLOAD(SDNode *N, SmallVectorImpl<SDValue> &Results,
                                   SelectionDAG &DAG) const {
   LoadSDNode *LD = cast<LoadSDNode>(N);
   EVT MemVT = LD->getMemoryVT();
-  assert(LD->isUnindexed() && "Loads should be unindexed at this point.");
 
   if (MemVT == MVT::i64 && Subtarget->hasV5TEOps() &&
       !Subtarget->isThumb1Only() && LD->isVolatile() &&
       LD->getAlign() >= Subtarget->getDualLoadStoreAlignment()) {
+    assert(LD->isUnindexed() && "Loads should be unindexed at this point.");
     SDLoc dl(N);
     SDValue Result = DAG.getMemIntrinsicNode(
         ARMISD::LDRD, dl, DAG.getVTList({MVT::i32, MVT::i32, MVT::Other}),
@@ -10067,6 +10199,12 @@ void ARMTargetLowering::LowerLOAD(SDNode *N, SmallVectorImpl<SDValue> &Results,
     SDValue Hi = Result.getValue(DAG.getDataLayout().isLittleEndian() ? 1 : 0);
     SDValue Pair = DAG.getNode(ISD::BUILD_PAIR, dl, MVT::i64, Lo, Hi);
     Results.append({Pair, Result.getValue(2)});
+  } else if ((MemVT == MVT::i32 || MemVT == MVT::i64)) {
+    auto Pair = LowerAEABIUnalignedLoad(SDValue(N, 0), DAG);
+    if (Pair.first) {
+      Results.push_back(Pair.first);
+      Results.push_back(Pair.second);
+    }
   }
 }
 
@@ -10108,15 +10246,15 @@ static SDValue LowerPredicateStore(SDValue Op, SelectionDAG &DAG) {
       ST->getMemOperand());
 }
 
-static SDValue LowerSTORE(SDValue Op, SelectionDAG &DAG,
-                          const ARMSubtarget *Subtarget) {
+SDValue ARMTargetLowering::LowerSTORE(SDValue Op, SelectionDAG &DAG,
+                                      const ARMSubtarget *Subtarget) const {
   StoreSDNode *ST = cast<StoreSDNode>(Op.getNode());
   EVT MemVT = ST->getMemoryVT();
-  assert(ST->isUnindexed() && "Stores should be unindexed at this point.");
 
   if (MemVT == MVT::i64 && Subtarget->hasV5TEOps() &&
       !Subtarget->isThumb1Only() && ST->isVolatile() &&
       ST->getAlign() >= Subtarget->getDualLoadStoreAlignment()) {
+    assert(ST->isUnindexed() && "Stores should be unindexed at this point.");
     SDNode *N = Op.getNode();
     SDLoc dl(N);
 
@@ -10136,8 +10274,9 @@ static SDValue LowerSTORE(SDValue Op, SelectionDAG &DAG,
              ((MemVT == MVT::v2i1 || MemVT == MVT::v4i1 || MemVT == MVT::v8i1 ||
                MemVT == MVT::v16i1))) {
     return LowerPredicateStore(Op, DAG);
+  } else if ((MemVT == MVT::i32 || MemVT == MVT::i64)) {
+    return LowerAEABIUnalignedStore(Op, DAG);
   }
-
   return SDValue();
 }
 
@@ -10669,8 +10808,19 @@ SDValue ARMTargetLowering::LowerOperation(SDValue Op, SelectionDAG &DAG) const {
   case ISD::UADDSAT:
   case ISD::USUBSAT:
     return LowerADDSUBSAT(Op, DAG, Subtarget);
-  case ISD::LOAD:
-    return LowerPredicateLoad(Op, DAG);
+  case ISD::LOAD: {
+    auto *LD = cast<LoadSDNode>(Op);
+    EVT MemVT = LD->getMemoryVT();
+    if (Subtarget->hasMVEIntegerOps() &&
+        ((MemVT == MVT::v2i1 || MemVT == MVT::v4i1 || MemVT == MVT::v8i1 ||
+          MemVT == MVT::v16i1)))
+      return LowerPredicateLoad(Op, DAG);
+
+    auto Pair = LowerAEABIUnalignedLoad(Op, DAG);
+    if (Pair.first)
+      return DAG.getMergeValues({Pair.first, Pair.second}, SDLoc(Pair.first));
+    return SDValue();
+  }
   case ISD::STORE:
     return LowerSTORE(Op, DAG, Subtarget);
   case ISD::MLOAD:
@@ -10811,6 +10961,9 @@ void ARMTargetLowering::ReplaceNodeResults(SDNode *N,
   case ISD::LOAD:
     LowerLOAD(N, Results, DAG);
     break;
+  case ISD::STORE:
+    Res = LowerAEABIUnalignedStore(SDValue(N, 0), DAG);
+    break;
   case ISD::TRUNCATE:
     Res = LowerTruncate(N, DAG, Subtarget);
     break;
@@ -19859,31 +20012,45 @@ ARMTargetLowering::getPreIndexedAddressParts(SDNode *N, SDValue &Base,
   EVT VT;
   SDValue Ptr;
   Align Alignment;
+  unsigned AS = 0;
   bool isSEXTLoad = false;
   bool IsMasked = false;
   if (LoadSDNode *LD = dyn_cast<LoadSDNode>(N)) {
     Ptr = LD->getBasePtr();
     VT = LD->getMemoryVT();
     Alignment = LD->getAlign();
+    AS = LD->getAddressSpace();
     isSEXTLoad = LD->getExtensionType() == ISD::SEXTLOAD;
   } else if (StoreSDNode *ST = dyn_cast<StoreSDNode>(N)) {
     Ptr = ST->getBasePtr();
     VT = ST->getMemoryVT();
     Alignment = ST->getAlign();
+    AS = ST->getAddressSpace();
   } else if (MaskedLoadSDNode *LD = dyn_cast<MaskedLoadSDNode>(N)) {
     Ptr = LD->getBasePtr();
     VT = LD->getMemoryVT();
     Alignment = LD->getAlign();
+    AS = LD->getAddressSpace();
     isSEXTLoad = LD->getExtensionType() == ISD::SEXTLOAD;
     IsMasked = true;
   } else if (MaskedStoreSDNode *ST = dyn_cast<MaskedStoreSDNode>(N)) {
     Ptr = ST->getBasePtr();
     VT = ST->getMemoryVT();
     Alignment = ST->getAlign();
+    AS = ST->getAddressSpace();
     IsMasked = true;
   } else
     return false;
 
+  unsigned Fast = 0;
+  if (!allowsMisalignedMemoryAccesses(VT, AS, Alignment,
+                                      MachineMemOperand::MONone, &Fast)) {
+    // Only generate post-increment or pre-increment forms when a real
+    // hardware instruction exists for them. Do not emit postinc/preinc
+    // if the operation will end up as a libcall.
+    return false;
+  }
+
   bool isInc;
   bool isLegal = false;
   if (VT.isVector())
diff --git a/llvm/lib/Target/ARM/ARMISelLowering.h b/llvm/lib/Target/ARM/ARMISelLowering.h
index bc2fec3c1bdb5..ae93fdf6d619b 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.h
+++ b/llvm/lib/Target/ARM/ARMISelLowering.h
@@ -919,10 +919,14 @@ class VectorType;
     SDValue LowerSPONENTRY(SDValue Op, SelectionDAG &DAG) const;
     void LowerLOAD(SDNode *N, SmallVectorImpl<SDValue> &Results,
                    SelectionDAG &DAG) const;
+    SDValue LowerSTORE(SDValue Op, SelectionDAG &DAG,
+                       const ARMSubtarget *Subtarget) const;
+    std::pair<SDValue, SDValue>
+    LowerAEABIUnalignedLoad(SDValue Op, SelectionDAG &DAG) const;
+    SDValue LowerAEABIUnalignedStore(SDValue Op, SelectionDAG &DAG) const;
     SDValue LowerFP_TO_BF16(SDValue Op, SelectionDAG &DAG) const;
     SDValue LowerCMP(SDValue Op, SelectionDAG &DAG) const;
     SDValue LowerABS(SDValue Op, SelectionDAG &DAG) const;
-
     Register getRegisterByName(const char* RegName, LLT VT,
                                const MachineFunction &MF) const override;
 
diff --git a/llvm/test/CodeGen/ARM/i64_volatile_load_store.ll b/llvm/test/CodeGen/ARM/i64_volatile_load_store.ll
index ca5fd2bc14f40..125326fd754fa 100644
--- a/llvm/test/CodeGen/ARM/i64_volatile_load_store.ll
+++ b/llvm/test/CodeGen/ARM/i64_volatile_load_store.ll
@@ -121,23 +121,22 @@ define void @test_unaligned() {
 ; CHECK-ARMV5TE-NEXT:    push {r4, r5, r6, lr}
 ; CHECK-ARMV5TE-NEXT:    ldr r0, .LCPI1_0
 ; CHECK-ARMV5TE-NEXT:    ldr r6, .LCPI1_1
-; CHECK-ARMV5TE-NEXT:    mov r1, r0
-; CHECK-ARMV5TE-NEXT:    ldrb lr, [r1, #4]!
-; CHECK-ARMV5TE-NEXT:    ldrb r3, [r1, #2]
-; CHECK-ARMV5TE-NEXT:    ldrb r12, [r1, #3]
-; CHECK-ARMV5TE-NEXT:    ldrb r1, [r0]
-; CHECK-ARMV5TE-NEXT:    ldrb r2, [r0, #1]
-; CHECK-ARMV5TE-NEXT:    ldrb r4, [r0, #2]
-; CHECK-ARMV5TE-NEXT:    ldrb r5, [r0, #3]
-; CHECK-ARMV5TE-NEXT:    ldrb r0, [r0, #5]
-; CHECK-ARMV5TE-NEXT:    strb r0, [r6, #5]
-; CHECK-ARMV5TE-NEXT:    strb r4, [r6, #2]
-; CHECK-ARMV5TE-NEXT:    strb r5, [r6, #3]
-; CHECK-ARMV5TE-NEXT:    strb r1, [r6]
-; CHECK-ARMV5TE-NEXT:    strb r2, [r6, #1]
-; CHECK-ARMV5TE-NEXT:    strb lr, [r6, #4]!
+; CHECK-ARMV5TE-NEXT:    ldrb r12, [r0]
+; CHECK-ARMV5TE-NEXT:    ldrb lr, [r0, #1]
+; CHECK-ARMV5TE-NEXT:    ldrb r3, [r0, #2]
+; CHECK-ARMV5TE-NEXT:    ldrb r1, [r0, #3]
+; CHECK-ARMV5TE-NEXT:    ldrb r2, [r0, #5]
+; CHECK-ARMV5TE-NEXT:    ldrb r4, [r0, #4]
+; CHECK-ARMV5TE-NEXT:    ldrb r5, [r0, #7]
+; CHECK-ARMV5TE-NEXT:    ldrb r0, [r0, #6]
+; CHECK-ARMV5TE-NEXT:    strb r0, [r6, #6]
+; CHECK-ARMV5TE-NEXT:    strb r5, [r6, #7]
+; CHECK-ARMV5TE-NEXT:    strb r4, [r6, #4]
+; CHECK-ARMV5TE-NEXT:    strb r2, [r6, #5]
 ; CHECK-ARMV5TE-NEXT:    strb r3, [r6, #2]
-; CHECK-ARMV5TE-NEXT:    strb r12, [r6, #3]
+; CHECK-ARMV5TE-NEXT:    strb r1, [r6, #3]
+; CHECK-ARMV5TE-NEXT:    strb r12, [r6]
+; CHECK-ARMV5TE-NEXT:    strb lr, [r6, #1]
 ; CHECK-ARMV5TE-NEXT:    pop {r4, r5, r6, pc}
 ; CHECK-ARMV5TE-NEXT:    .p2align 2
 ; CHECK-ARMV5TE-NEXT:  @ %bb.1:
@@ -164,23 +163,22 @@ define void @test_unaligned() {
 ; CHECK-ARMV4T-NEXT:    push {r4, r5, r6, lr}
 ; CHECK-ARMV4T-NEXT:    ldr r0, .LCPI1_0
 ; CHECK-ARMV4T-NEXT:    ldr r6, .LCPI1_1
-; CHECK-ARMV4T-NEXT:    mov r1, r0
-; CHECK-ARMV4T-NEXT:    ldrb lr, [r1, #4]!
-; CHECK-ARMV4T-NEXT:    ldrb r3, [r1, #2]
-; CHECK-ARMV4T-NEXT:    ldrb r12, [r1, #3]
-; CHECK-ARMV4T-NEXT:    ldrb r1, [r0]
-; CHECK-ARMV4T-NEXT:    ldrb r2, [r0, #1]
-; CHECK-ARMV4T-NEXT:    ldrb r4, [r0, #2]
-; CHECK-ARMV4T-NEXT:    ldrb r5, [r0, #3]
-; CHECK-ARMV4T-NEXT:    ldrb r0, [r0, #5]
-; CHECK-ARMV4T-NEXT:    strb r0, [r6, #5]
-; CHECK-ARMV4T-NEXT:    strb r4, [r6, #2]
-; CHECK-ARMV4T-NEXT:    strb r5, [r6, #3]
-; CHECK-ARMV4T-NEXT:    strb r1, [r6]
-; CHECK-ARMV4T-NEXT:    strb r2, [r6, #1]
-; CHECK-ARMV4T-NEXT:    strb lr, [r6, #4]!
+; CHECK-ARMV4T-NEXT:    ldrb r12, [r0]
+; CHECK-ARMV4T-NEXT:    ldrb lr, [r0, #1]
+; CHECK-ARMV4T-NEXT:    ldrb r3, [r0, #2]
+; CHECK-ARMV4T-NEXT:    ldrb r1, [r0, #3]
+; CHECK-ARMV4T-NEXT:    ldrb r2, [r0, #5]
+; CHECK-ARMV4T-NEXT:    ldrb r4, [r0, #4]
+; CHECK-ARMV4T-NEXT:    ldrb r5, [r0, #7]
+; CHECK-ARMV4T-NEXT:    ldrb r0, [r0, #6]
+; CHECK-ARMV4T-NEXT:    strb r0, [r6, #6]
+; CHECK-ARMV4T-NEXT:    strb r5, [r6, #7]
+; CHECK-ARMV4T-NEXT:    strb r4, [r6, #4]
+; CHECK-ARMV4T-NEXT:    strb r2, [r6, #5]
 ; CHECK-ARMV4T-NEXT:    strb r3, [r6, #2]
-; CHECK-ARMV4T-NEXT:    strb r12, [r6, #3]
+; CHECK-ARMV4T-NEXT:    strb r1, [r6, #3]
+; CHECK-ARMV4T-NEXT:    strb r12, [r6]
+; CHECK-ARMV4T-NEXT:    strb lr, [r6, #1]
 ; CHECK-ARMV4T-NEXT:    pop {r4, r5, r6, lr}
 ; CHECK-ARMV4T-NEXT:    bx lr
 ; CHECK-ARMV4T-NEXT:    .p2align 2
@@ -210,23 +208,22 @@ define void @test_unaligned() {
 ; CHECK-ARMV7-STRICT-NEXT:    movw r6, :lower16:y_unaligned
 ; CHECK-ARMV7-STRICT-NEXT:    movt r0, :upper16:x_unaligned
 ; CHECK-ARMV7-STRICT-NEXT:    movt r6, :upper16:y_unaligned
-; CHECK-ARMV7-STRICT-NEXT:    mov r1, r0
-; CHECK-ARMV7-STRICT-NEXT:    ldrb r12, [r1, #4]!
-; CHECK-ARMV7-STRICT-NEXT:    ldrb r3, [r0]
+; CHECK-ARMV7-STRICT-NEXT:    ldrb r12, [r0]
 ; CHECK-ARMV7-STRICT-NEXT:    ldrb lr, [r0, #1]
-; CHECK-ARMV7-STRICT-NEXT:    ldrb r2, [r0, #2]
-; CHECK-ARMV7-STRICT-NEXT:    ldrb r4, [r0, #3]
-; CHECK-ARMV7-STRICT-NEXT:    ldrb r0, [r0, #5]
-; CHECK-ARMV7-STRICT-NEXT:    ldrb r5, [r1, #2]
-; CHECK-ARMV7-STRICT-NEXT:    ldrb r1, [r1, #3]
-; CHECK-ARMV7-STRICT-NEXT:    strb r0, [r6, #5]
-; CHECK-ARMV7-STRICT-NEXT:    strb r2, [r6, #2]
-; CHECK-ARMV7-STRICT-NEXT:    strb r4, [r6, #3]
-; CHECK-ARMV7-STRICT-NEXT:    strb r3, [r6]
-; CHECK-ARMV7-STRICT-NEXT:    strb lr, [r6, #1]
-; CHECK-ARMV7-STRICT-NEXT:    strb r12, [r6, #4]!
-; CHECK-ARMV7-STRICT-NEXT:    strb r5, [r6, #2]
+; CHECK-ARMV7-STRICT-NEXT:    ldrb r3, [r0, #2]
+; CHECK-ARMV7-STRICT-NEXT:    ldrb r1, [r0, #3]
+; CHECK-ARMV7-STRICT-NEXT:    ldrb r2, [r0, #5]
+; CHECK-ARMV7-STRICT-NEXT:    ldrb r4, [r0, #4]
+; CHECK-ARMV7-STRICT-NEXT:    ldrb r5, [r0, #7]
+; CHECK-ARMV7-STRICT-NEXT:    ldrb r0, [r0, #6]
+; CHECK-ARMV7-STRICT-NEXT:    strb r0, [r6, #6]
+; CHECK-ARMV7-STRICT-NEXT:    strb r5, [r6, #7]
+; CHECK-ARMV7-STRICT-NEXT:    strb r4, [r6, #4]
+; CHECK-ARMV7-STRICT-NEXT:    strb r2, [r6, #5]
+; CHECK-ARMV7-STRICT-NEXT:    strb r3, [r6, #2]
 ; CHECK-ARMV7-STRICT-NEXT:    strb r1, [r6, #3]
+; CHECK-ARMV7-STRICT-NEXT:    strb r12, [r6]
+; CHECK-ARMV7-STRICT-NEXT:    strb lr, [r6, #1]
 ; CHECK-ARMV7-STRICT-NEXT:    pop {r4, r5, r6, pc}
 ;
 ; CHECK-ARMV6-LABEL: test_unaligned:
@@ -251,23 +248,22 @@ define void @test_unaligned() {
 ; CHECK-ARMV6-STRICT-NEXT:    push {r4, r5, r6, lr}
 ; CHECK-ARMV6-STRICT-NEXT:    ldr r0, .LCPI1_0
 ; CHECK-ARMV6-STRICT-NEXT:    ldr r6, .LCPI1_1
-; CHECK-ARMV6-STRICT-NEXT:    mov r1, r0
-; CHECK-ARMV6-STRICT-NEXT:    ldrb lr, [r1, #4]!
-; CHECK-ARMV6-STRICT-NEXT:    ldrb r3, [r1, #2]
-; CHECK-ARMV6-STRICT-NEXT:    ldrb r12, [r1, #3]
-; CHECK-ARMV6-STRICT-NEXT:    ldrb r1, [r0]
-; CHECK-ARMV6-STRICT-NEXT:    ldrb r2, [r0, #1]
-; CHECK-ARMV6-STRICT-NEXT:    ldrb r4, [r0, #2]
-; CHECK-ARMV6-STRICT-NEXT:    ldrb r5, [r0, #3]
-; CHECK-ARMV6-STRICT-NEXT:    ldrb r0, [r0, #5]
-; CHECK-ARMV6-STRICT-NEXT:    strb r0, [r6, #5]
-; CHECK-ARMV6-STRICT-NEXT:    strb r4, [r6, #2]
-; CHECK-ARMV6-STRICT-NEXT:    strb r5, [r6, #3]
-; CHECK-ARMV6-STRICT-NEXT:    strb r1, [r6]
-; CHECK-ARMV6-STRICT-NEXT:    strb r2, [r6, #1]
-; CHECK-ARMV6-STRICT-NEXT:    strb lr, [r6, #4]!
+; CHECK-ARMV6-STRICT-NEXT:    ldrb r12, [r0]
+; CHECK-ARMV6-STRICT-NEXT:    ldrb lr, [r0, #1]
+; CHECK-ARMV6-STRICT-NEXT:    ldrb r3, [r0, #2]
+; CHECK-ARMV6-STRICT-NEXT:    ldrb r1, [r0, #3]
+; CHECK-ARMV6-STRICT-NEXT:    ldrb r2, [r0, #5]
+; CHECK-ARMV6-STRICT-NEXT:    ldrb r4, [r0, #4]
+; CHECK-ARMV6-STRICT-NEXT:    ldrb r5, [r0, #7]
+; CHECK-ARMV6-STRICT-NEXT:    ldrb r0, [r0, #6]
+; CHECK-ARMV6-STRICT-NEXT:    strb r0, [r6, #6]
+; CHECK-ARMV6-STRICT-NEXT:    strb r5, [r6, #7]
+; CHECK-ARMV6-STRICT-NEXT:    strb r4, [r6, #4]
+; CHECK-ARMV6-STRICT-NEXT:    strb r2, [r6, #5]
 ; CHECK-ARMV6-STRICT-NEXT:    strb r3, [r6, #2]
-; CHECK-ARMV6-STRICT-NEXT:    strb r12, [r6, #3]
+; CHECK-ARMV6-STRICT-NEXT:    strb r1, [r6, #3]
+; CHECK-ARMV6-STRICT-NEXT:    strb r12, [r6]
+; CHECK-ARMV6-STRICT-NEXT:    strb lr, [r6, #1]
 ; CHECK-ARMV6-STRICT-NEXT:    pop {r4, r5, r6, pc}
 ; CHECK-ARMV6-STRICT-NEXT:    .p2align 2
 ; CHECK-ARMV6-STRICT-NEXT:  @ %bb.1:
diff --git a/llvm/test/CodeGen/ARM/unaligned_load_store_aeabi.ll b/llvm/test/CodeGen/ARM/unaligned_load_store_aeabi.ll
new file mode 100644
index 0000000000000..0f1adc55139c7
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/unaligned_load_store_aeabi.ll
@@ -0,0 +1,425 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=thumbv6m-eabi -mattr=+strict-align %s -o - | FileCheck %s -check-prefix=CHECK-V6M
+; RUN: llc -mtriple=thumbv7m-eabi -mattr=+strict-align %s -o - | FileCheck %s -check-prefix=CHECK-V7M
+; RUN: llc -mtriple=thumbv7m-eabi -mattr=-strict-align %s -o - | FileCheck %s -check-prefix=CHECK-ALIGNED
+
+define void @loadstore4_align1(i32* %a, i32* %b) nounwind optsize minsize {
+; CHECK-V6M-LABEL: loadstore4_a...
[truncated]

Comment thread llvm/lib/Target/ARM/ARMISelLowering.cpp Outdated
Comment thread llvm/lib/Target/ARM/ARMISelLowering.cpp Outdated
1. Updated implementation to route all libcall handling
through RuntimeLibcalls, as recommended.
2. Logic has been updated so the behaviour now applies
   only for -Oz, not for both -Os and -Oz.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change to i64_volatile_load_store.ll comes from this change? That's fine, I guess.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this patch is what introduced the changes to this test.

Comment thread llvm/lib/Target/ARM/ARMISelLowering.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra parentheses

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still there in latest version?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I have cleaned up couple of the redundant parentheses as well. I’m not sure if there are any others I may have missed — please let me know if you had specific ones in mind.

Comment thread llvm/lib/Target/ARM/ARMISelLowering.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra parentheses

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure we have test coverage for optsize without minsize.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

   1. Removed extra parentheses.
   2. Test unaligned_load_store_aeabi.ll is updated
      to have coverage for both optsize and minsize cases.
   3. Forwards the chanis as well in the makelibcall
      to ensure correct scheduling and ordering.
@github-actions

github-actions Bot commented Jan 26, 2026

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

@efriedma-quic efriedma-quic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@simpal01

Copy link
Copy Markdown
Contributor Author

Thanks a lot for reviewing and approving — much appreciated!

There’s a related change in #167913 that builds on the same area. If you have time, I’d really appreciate your review there as well.

@simpal01
simpal01 merged commit 09a6842 into llvm:main Feb 2, 2026
11 checks passed
@llvm-ci

llvm-ci commented Feb 2, 2026

Copy link
Copy Markdown

LLVM Buildbot has detected a new failure on builder clang-ppc64le-linux-test-suite running on ppc64le-clang-test-suite while building llvm at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/95/builds/19944

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'SanitizerCommon-ubsan-powerpc64le-Linux :: Linux/getpwnam_r_invalid_user.cpp' FAILED ********************
Exit Code: -6

Command Output (stdout):
--
# RUN: at line 2
/home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-test-suite/clang-ppc64le-test-suite/build/./bin/clang  --driver-mode=g++ -gline-tables-only -fsanitize=undefined  -m64 -fno-function-sections -funwind-tables  -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-test-suite/clang-ppc64le-test-suite/llvm-project/compiler-rt/test -ldl -O0 -g /home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-test-suite/clang-ppc64le-test-suite/llvm-project/compiler-rt/test/sanitizer_common/TestCases/Linux/getpwnam_r_invalid_user.cpp -o /home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-test-suite/clang-ppc64le-test-suite/build/runtimes/runtimes-bins/compiler-rt/test/sanitizer_common/ubsan-powerpc64le-Linux/Linux/Output/getpwnam_r_invalid_user.cpp.tmp &&  /home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-test-suite/clang-ppc64le-test-suite/build/runtimes/runtimes-bins/compiler-rt/test/sanitizer_common/ubsan-powerpc64le-Linux/Linux/Output/getpwnam_r_invalid_user.cpp.tmp
# executed command: /home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-test-suite/clang-ppc64le-test-suite/build/./bin/clang --driver-mode=g++ -gline-tables-only -fsanitize=undefined -m64 -fno-function-sections -funwind-tables -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-test-suite/clang-ppc64le-test-suite/llvm-project/compiler-rt/test -ldl -O0 -g /home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-test-suite/clang-ppc64le-test-suite/llvm-project/compiler-rt/test/sanitizer_common/TestCases/Linux/getpwnam_r_invalid_user.cpp -o /home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-test-suite/clang-ppc64le-test-suite/build/runtimes/runtimes-bins/compiler-rt/test/sanitizer_common/ubsan-powerpc64le-Linux/Linux/Output/getpwnam_r_invalid_user.cpp.tmp
# executed command: /home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-test-suite/clang-ppc64le-test-suite/build/runtimes/runtimes-bins/compiler-rt/test/sanitizer_common/ubsan-powerpc64le-Linux/Linux/Output/getpwnam_r_invalid_user.cpp.tmp
# .---command stderr------------
# | Result: 110
# | getpwnam_r_invalid_user.cpp.tmp: /home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-test-suite/clang-ppc64le-test-suite/llvm-project/compiler-rt/test/sanitizer_common/TestCases/Linux/getpwnam_r_invalid_user.cpp:19: int main(): Assertion `res == 0 || res == ENOENT' failed.
# `-----------------------------
# error: command failed with exit status: -6

--

********************


rishabhmadan19 pushed a commit to rishabhmadan19/llvm-project that referenced this pull request Feb 9, 2026
When targeting architectures that do not support unaligned memory
accesses or when explictly pass -mno-unaligned-access, it requires the
compiler to expand each unaligned load/store into an inline sequences.
For 32-bit operations this typically involves:

	1. 4× LDRB (or 2× LDRH),
	2. multiple shift/or instructions

These sequences are emitted at every unaligned access site, and
therefore contribute significant code size in workloads that touch
packed or misaligned structures.

When compiling with -Oz and in combination with -mno-unaligned-access,
this patch lowers unaligned 32 bit and 64 bit loads and stores to below
AEABI heper calls:
```
         __aeabi_uread4
	__aeabi_uread8
	__aeabi_uwrite4
	__aeabi_uwrite8
```

And it provide a way to perform unaligned memory accesses on targets
that do not support them, such as ARMv6-M or when compiling with
-mno-unaligned-access. Although each use introduces a function call
making it less straightforward than using raw loads and stores the call
itself is often much smaller than the compiler emitted sequence of
multiple ldrb/strb operations. As a result, these helpers can greatly
reduce code-size providing they are invoked more than once across a
program.

1. Functions become smaller in AEABI mode once they contain more than a
few unaligned accesses.
2. The total image .text size becomes smaller whenever multiple
functions call the same helpers.

This PR is derived from https://reviews.llvm.org/D57595, with some minor
changes.
 Co-authored-by: David Green
simpal01 added a commit to arm/arm-toolchain that referenced this pull request Feb 24, 2026
… unaligned i32/i64 loads/stores to __aeabi_uread/uwrite{4,8} (#734)

Downstream issue:#727

This was committed upstream after the LLVM 22 branch was created. Mostly
upstream will not accept it into the 22 branch, as it is not considered
critical enough for backporting.

When targeting ARM (in particular architectures or variants that do not
allow unaligned memory accesses, e.g., with -mno-unaligned-access or
older cores like ARMv6-M) the backend currently handles unaligned loads
or stores by expanding them into sequences of byte/half-word
loads/stores.

This change proposes to detect unaligned loads/stores of size 4 bytes
(i32) or 8 bytes (i64) when alignment is insufficient, and instead emit
calls to the AEABI helper functions by llvm:

• __aeabi_uread4, __aeabi_uread8 for unaligned reads

• __aeabi_uwrite4, __aeabi_uwrite8 for unaligned writes

By lowering to these functions, code size can be reduced compared to
in-lined byte/half-word sequences providing they are invoked more than
once across a program. This also needs to add the implementations for
the __aeabi_uread and __aeabi_uwrite helper functions to compiler-rt.

List of upstream PRs:

- llvm/llvm-project#167913
- llvm/llvm-project#172672
anemet added a commit to anemet/llvm-project that referenced this pull request Jul 6, 2026
PR llvm#172672 added ARMTargetLowering::LowerAEABIUnalignedLoad/Store
which lowers some of the unaligned i32/i64 stores to
__aeabi_u{read,write}{4,8}. The libcall is emitted unconditionally,
with no check whether the target environment actually has these
AEABI helpers.

We don't have it for Apple/MachO which leads to a compiler crash.

rdar://175136625
anemet added a commit to anemet/llvm-project that referenced this pull request Jul 6, 2026
PR llvm#172672 added ARMTargetLowering::LowerAEABIUnalignedLoad/Store
which lowers some of the unaligned i32/i64 stores to
__aeabi_u{read,write}{4,8}. The libcall is emitted unconditionally,
with no check whether the target environment actually has these
AEABI helpers.

We don't have it for Apple/MachO which leads to a compiler crash.

rdar://175136625
anemet added a commit that referenced this pull request Jul 7, 2026
PR #172672 added ARMTargetLowering::LowerAEABIUnalignedLoad/Store which
lowers some of the unaligned i32/i64 stores to
__aeabi_u{read,write}{4,8}. The libcall is emitted unconditionally, with
no check whether the target environment actually has these AEABI
helpers.

We don't have it for Apple/MachO which leads to a compiler crash.

rdar://175136625
gandhi56 pushed a commit that referenced this pull request Jul 9, 2026
PR #172672 added ARMTargetLowering::LowerAEABIUnalignedLoad/Store which
lowers some of the unaligned i32/i64 stores to
__aeabi_u{read,write}{4,8}. The libcall is emitted unconditionally, with
no check whether the target environment actually has these AEABI
helpers.

We don't have it for Apple/MachO which leads to a compiler crash.

rdar://175136625
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants