Skip to content

Conversation

@dgg5503
Copy link
Contributor

@dgg5503 dgg5503 commented Oct 24, 2025

This change enables validation checks against the following two ARM atomic builtins:

__builtin_arm_ldrexd
__builtin_arm_strexd

Previously, no checks existed for these builtins, so under a release compiler, it would be possible to emit ldrexd/strexd under ARM targets which set the LDREX mask (returned via getARMLDREXMask) to signify these as unsupported instructions.

For example, the following would compile with errors:

> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strex(42, &num);
    return __builtin_arm_ldrex(&num);
}
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('volatile long long *' invalid)
    3 |     __builtin_arm_strex(42, &num);
      |     ^
atomics.c:4:12: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('const volatile long long *' invalid)
    4 |     return __builtin_arm_ldrex(&num);
      |            ^
2 errors generated.

However, a similar program would compile without errors:

> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strexd(42, &num);
    return __builtin_arm_ldrexd(&num);
}
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
...
        strexd  r1, r2, r3, [r0]
        ldrexd  r0, r1, [r0]
...

With this change, we now have appropriate compile-time errors:

> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: load and store exclusive builtins are not available on this architecture
    3 |     __builtin_arm_strexd(42, &num);
      |     ^                        ~~~~
atomics.c:4:12: error: load and store exclusive builtins are not available on this architecture
    4 |     return __builtin_arm_ldrexd(&num);
      |            ^                    ~~~~
2 errors generated.

If possible, it might be better to remove
__builtin_arm_ldrexd/__builtin_arm_strexd in favor of the generic versions which seem to already support double-word length. If this is preferred, I'd be happy to close this pull-request and open a new one with the intrinsics removed.

This change enables validation checks against the following two ARM
atomic builtins:
```
__builtin_arm_ldrexd
__builtin_arm_strexd
```

Previously, no checks existed for these builtins, so under a release
compiler, it would be possible to emit `ldrexd`/`strexd` under ARM targets
which set the LDREX mask (returned via `getARMLDREXMask`) to signify
these as unsupported instructions.

For example, the following would compile with errors:
```c
> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strex(42, &num);
    return __builtin_arm_ldrex(&num);
}
```
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('volatile long long *' invalid)
    3 |     __builtin_arm_strex(42, &num);
      |     ^
atomics.c:4:12: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('const volatile long long *' invalid)
    4 |     return __builtin_arm_ldrex(&num);
      |            ^
2 errors generated.
```

However, a similar program would compile without errors:
```c
> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strexd(42, &num);
    return __builtin_arm_ldrexd(&num);
}
```
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
...
        strexd  r1, r2, r3, [r0]
        ldrexd  r0, r1, [r0]
...
```

With this change, we now have appropriate compile-time errors:
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: load and store exclusive builtins are not available on this architecture
    3 |     __builtin_arm_strexd(42, &num);
      |     ^                        ~~~~
atomics.c:4:12: error: load and store exclusive builtins are not available on this architecture
    4 |     return __builtin_arm_ldrexd(&num);
      |            ^                    ~~~~
2 errors generated.
```

If possible, it might be better to remove
`__builtin_arm_ldrexd`/`__builtin_arm_strexd` in favor of the generic versions
which seem to already support double-word length. If this is preferred, I'd be
happy to close this pull-request and open a new one with the intrinsics
removed.
@llvmbot llvmbot added clang Clang issues not falling into any other category backend:ARM backend:AArch64 clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Oct 24, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 24, 2025

@llvm/pr-subscribers-backend-aarch64

@llvm/pr-subscribers-clang

Author: Douglas (dgg5503)

Changes

This change enables validation checks against the following two ARM atomic builtins:

__builtin_arm_ldrexd
__builtin_arm_strexd

Previously, no checks existed for these builtins, so under a release compiler, it would be possible to emit ldrexd/strexd under ARM targets which set the LDREX mask (returned via getARMLDREXMask) to signify these as unsupported instructions.

For example, the following would compile with errors:

> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strex(42, &num);
    return __builtin_arm_ldrex(&num);
}
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('volatile long long *' invalid)
    3 |     __builtin_arm_strex(42, &num);
      |     ^
atomics.c:4:12: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('const volatile long long *' invalid)
    4 |     return __builtin_arm_ldrex(&num);
      |            ^
2 errors generated.

However, a similar program would compile without errors:

> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strexd(42, &num);
    return __builtin_arm_ldrexd(&num);
}
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
...
        strexd  r1, r2, r3, [r0]
        ldrexd  r0, r1, [r0]
...

With this change, we now have appropriate compile-time errors:

> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: load and store exclusive builtins are not available on this architecture
    3 |     __builtin_arm_strexd(42, &num);
      |     ^                        ~~~~
atomics.c:4:12: error: load and store exclusive builtins are not available on this architecture
    4 |     return __builtin_arm_ldrexd(&num);
      |            ^                    ~~~~
2 errors generated.

If possible, it might be better to remove
__builtin_arm_ldrexd/__builtin_arm_strexd in favor of the generic versions which seem to already support double-word length. If this is preferred, I'd be happy to close this pull-request and open a new one with the intrinsics removed.


Full diff: https://github.com/llvm/llvm-project/pull/164919.diff

8 Files Affected:

  • (modified) clang/include/clang/Basic/BuiltinsARM.def (+2-2)
  • (modified) clang/lib/Sema/SemaARM.cpp (+12)
  • (modified) clang/test/CodeGen/builtins-arm-exclusive.c (+46)
  • (modified) clang/test/CodeGenCXX/builtins-arm-exclusive.cpp (+32)
  • (modified) clang/test/Sema/builtins-arm-exclusive-124.c (+24)
  • (modified) clang/test/Sema/builtins-arm-exclusive-4.c (+18)
  • (modified) clang/test/Sema/builtins-arm-exclusive-none.c (+19-1)
  • (modified) clang/test/Sema/builtins-arm-exclusive.c (+52-1)
diff --git a/clang/include/clang/Basic/BuiltinsARM.def b/clang/include/clang/Basic/BuiltinsARM.def
index 2592e25e95c37..cdcc0d0d65e20 100644
--- a/clang/include/clang/Basic/BuiltinsARM.def
+++ b/clang/include/clang/Basic/BuiltinsARM.def
@@ -125,8 +125,8 @@ BUILTIN(__builtin_arm_cls, "UiZUi", "nc")
 BUILTIN(__builtin_arm_cls64, "UiWUi", "nc")
 
 // Store and load exclusive
-BUILTIN(__builtin_arm_ldrexd, "LLUiv*", "")
-BUILTIN(__builtin_arm_strexd, "iLLUiv*", "")
+BUILTIN(__builtin_arm_ldrexd, "v.", "t")
+BUILTIN(__builtin_arm_strexd, "i.", "t")
 
 BUILTIN(__builtin_arm_ldrex, "v.", "t")
 BUILTIN(__builtin_arm_ldaex, "v.", "t")
diff --git a/clang/lib/Sema/SemaARM.cpp b/clang/lib/Sema/SemaARM.cpp
index 1c7c832d7edfa..20cfa09754384 100644
--- a/clang/lib/Sema/SemaARM.cpp
+++ b/clang/lib/Sema/SemaARM.cpp
@@ -850,8 +850,10 @@ bool SemaARM::CheckARMBuiltinExclusiveCall(const TargetInfo &TI,
                                            unsigned BuiltinID,
                                            CallExpr *TheCall) {
   assert((BuiltinID == ARM::BI__builtin_arm_ldrex ||
+          BuiltinID == ARM::BI__builtin_arm_ldrexd ||
           BuiltinID == ARM::BI__builtin_arm_ldaex ||
           BuiltinID == ARM::BI__builtin_arm_strex ||
+          BuiltinID == ARM::BI__builtin_arm_strexd ||
           BuiltinID == ARM::BI__builtin_arm_stlex ||
           BuiltinID == AArch64::BI__builtin_arm_ldrex ||
           BuiltinID == AArch64::BI__builtin_arm_ldaex ||
@@ -859,9 +861,12 @@ bool SemaARM::CheckARMBuiltinExclusiveCall(const TargetInfo &TI,
           BuiltinID == AArch64::BI__builtin_arm_stlex) &&
          "unexpected ARM builtin");
   bool IsLdrex = BuiltinID == ARM::BI__builtin_arm_ldrex ||
+                 BuiltinID == ARM::BI__builtin_arm_ldrexd ||
                  BuiltinID == ARM::BI__builtin_arm_ldaex ||
                  BuiltinID == AArch64::BI__builtin_arm_ldrex ||
                  BuiltinID == AArch64::BI__builtin_arm_ldaex;
+  bool IsDoubleWord = BuiltinID == ARM::BI__builtin_arm_ldrexd ||
+                      BuiltinID == ARM::BI__builtin_arm_strexd;
 
   ASTContext &Context = getASTContext();
   DeclRefExpr *DRE =
@@ -928,6 +933,11 @@ bool SemaARM::CheckARMBuiltinExclusiveCall(const TargetInfo &TI,
   if (!TI.getTriple().isAArch64()) {
     unsigned Mask = TI.getARMLDREXMask();
     unsigned Bits = Context.getTypeSize(ValType);
+    if (IsDoubleWord) {
+      // Explicit request for ldrexd/strexd means only double word sizes
+      // supported if the target supports them.
+      Mask &= TargetInfo::ARM_LDREX_D;
+    }
     bool Supported =
         (llvm::isPowerOf2_64(Bits)) && Bits >= 8 && (Mask & (Bits / 8));
 
@@ -1013,8 +1023,10 @@ bool SemaARM::CheckARMBuiltinFunctionCall(const TargetInfo &TI,
                                           unsigned BuiltinID,
                                           CallExpr *TheCall) {
   if (BuiltinID == ARM::BI__builtin_arm_ldrex ||
+      BuiltinID == ARM::BI__builtin_arm_ldrexd ||
       BuiltinID == ARM::BI__builtin_arm_ldaex ||
       BuiltinID == ARM::BI__builtin_arm_strex ||
+      BuiltinID == ARM::BI__builtin_arm_strexd ||
       BuiltinID == ARM::BI__builtin_arm_stlex) {
     return CheckARMBuiltinExclusiveCall(TI, BuiltinID, TheCall);
   }
diff --git a/clang/test/CodeGen/builtins-arm-exclusive.c b/clang/test/CodeGen/builtins-arm-exclusive.c
index d2aaf26502a3d..f27dcfc81f34b 100644
--- a/clang/test/CodeGen/builtins-arm-exclusive.c
+++ b/clang/test/CodeGen/builtins-arm-exclusive.c
@@ -312,3 +312,49 @@ int test_stlex_128(__int128 *addr, __int128 val) {
 }
 
 #endif
+
+#ifdef __arm__
+// ARM exclusive atomic builtins
+
+int test_ldrexd(char *addr, long long *addr64, float *addrfloat) {
+// CHECK-LABEL: @test_ldrexd
+  int sum = 0;
+  sum += __builtin_arm_ldrexd((long long *)addr);
+// CHECK: call { i32, i32 } @llvm.arm.ldrexd(ptr %addr)
+
+  sum += __builtin_arm_ldrexd(addr64);
+// CHECK: call { i32, i32 } @llvm.arm.ldrexd(ptr %addr64)
+
+  sum += __builtin_arm_ldrexd((double *)addr);
+// CHECK: [[STRUCTRES:%.*]] = call { i32, i32 } @llvm.arm.ldrexd(ptr %addr)
+// CHECK: [[RESHI:%.*]] = extractvalue { i32, i32 } [[STRUCTRES]], 1
+// CHECK: [[RESLO:%.*]] = extractvalue { i32, i32 } [[STRUCTRES]], 0
+// CHECK: [[RESHI64:%.*]] = zext i32 [[RESHI]] to i64
+// CHECK: [[RESLO64:%.*]] = zext i32 [[RESLO]] to i64
+// CHECK: [[RESHIHI:%.*]] = shl nuw i64 [[RESHI64]], 32
+// CHECK: [[INTRES:%.*]] = or i64 [[RESHIHI]], [[RESLO64]]
+
+  return sum;
+}
+
+int test_strexd(char *addr) {
+// CHECK-LABEL: @test_strexd
+  int res = 0;
+  res |= __builtin_arm_strexd(42, (long long *)addr);
+// CHECK: store i64 42, ptr [[TMP:%.*]], align 8
+// CHECK: [[LOHI:%.*]] = load { i32, i32 }, ptr [[TMP]]
+// CHECK: [[LO:%.*]] = extractvalue { i32, i32 } [[LOHI]], 0
+// CHECK: [[HI:%.*]] = extractvalue { i32, i32 } [[LOHI]], 1
+// CHECK: call i32 @llvm.arm.strexd(i32 [[LO]], i32 [[HI]], ptr %addr)
+
+  res |= __builtin_arm_strexd(3.14159, (double *)addr);
+// CHECK: store double 3.141590e+00, ptr [[TMP:%.*]], align 8
+// CHECK: [[LOHI:%.*]] = load { i32, i32 }, ptr [[TMP]]
+// CHECK: [[LO:%.*]] = extractvalue { i32, i32 } [[LOHI]], 0
+// CHECK: [[HI:%.*]] = extractvalue { i32, i32 } [[LOHI]], 1
+// CHECK: call i32 @llvm.arm.strexd(i32 [[LO]], i32 [[HI]], ptr %addr)
+
+  return res;
+}
+
+#endif
diff --git a/clang/test/CodeGenCXX/builtins-arm-exclusive.cpp b/clang/test/CodeGenCXX/builtins-arm-exclusive.cpp
index d30631f6ad3bd..ca271935a90f1 100644
--- a/clang/test/CodeGenCXX/builtins-arm-exclusive.cpp
+++ b/clang/test/CodeGenCXX/builtins-arm-exclusive.cpp
@@ -22,3 +22,35 @@ void test_ldrex() {
 void tset_strex() {
   __builtin_arm_strex(true, &b);
 }
+
+#ifdef __arm__
+// ARM exclusive atomic builtins
+
+long long c;
+
+// CHECK-LABEL: @_Z11test_ldrexdv()
+// CHECK: [[STRUCTRES:%.*]] = call { i32, i32 } @llvm.arm.ldrexd(ptr @c)
+// CHECK: [[RESHI:%.*]] = extractvalue { i32, i32 } [[STRUCTRES]], 1
+// CHECK: [[RESLO:%.*]] = extractvalue { i32, i32 } [[STRUCTRES]], 0
+// CHECK: [[RESHI64:%.*]] = zext i32 [[RESHI]] to i64
+// CHECK: [[RESLO64:%.*]] = zext i32 [[RESLO]] to i64
+// CHECK: [[RESHIHI:%.*]] = shl nuw i64 [[RESHI64]], 32
+// CHECK: [[INTRES:%.*]] = or i64 [[RESHIHI]], [[RESLO64]]
+// CHECK: store i64 [[INTRES]], ptr @c, align 8
+
+void test_ldrexd() {
+  c = __builtin_arm_ldrexd(&c);
+}
+
+// CHECK-LABEL: @_Z11tset_strexdv()
+// CHECK: store i64 42, ptr [[TMP:%.*]], align 8
+// CHECK: [[LOHI:%.*]] = load { i32, i32 }, ptr [[TMP]]
+// CHECK: [[LO:%.*]] = extractvalue { i32, i32 } [[LOHI]], 0
+// CHECK: [[HI:%.*]] = extractvalue { i32, i32 } [[LOHI]], 1
+// CHECK: %{{.*}} = call i32 @llvm.arm.strexd(i32 [[LO]], i32 [[HI]], ptr @c)
+
+void tset_strexd() {
+  __builtin_arm_strexd(42, &c);
+}
+
+#endif
diff --git a/clang/test/Sema/builtins-arm-exclusive-124.c b/clang/test/Sema/builtins-arm-exclusive-124.c
index 013ae3f41ee7f..1d7d85a5a7416 100644
--- a/clang/test/Sema/builtins-arm-exclusive-124.c
+++ b/clang/test/Sema/builtins-arm-exclusive-124.c
@@ -24,3 +24,27 @@ int test_strex(char *addr) {
   res |= __builtin_arm_strex(42, (long long *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 1,2 or 4 byte type}}
   return res;
 }
+
+int test_ldrexd(char *addr) {
+  int sum = 0;
+  sum += __builtin_arm_ldrexd(addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((unsigned long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((float *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((double *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return sum;
+}
+
+int test_strexd(char *addr) {
+  int res = 0;
+  res |= __builtin_arm_strexd(4, addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (unsigned long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(2.71828f, (float *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(3.14159, (double *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return res;
+}
diff --git a/clang/test/Sema/builtins-arm-exclusive-4.c b/clang/test/Sema/builtins-arm-exclusive-4.c
index 68f01f5416616..0d31ce6f03769 100644
--- a/clang/test/Sema/builtins-arm-exclusive-4.c
+++ b/clang/test/Sema/builtins-arm-exclusive-4.c
@@ -20,3 +20,21 @@ int test_strex(char *addr) {
   res |= __builtin_arm_strex(42, (long long *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 4 byte type}}
   return res;
 }
+
+int test_ldrexd(char *addr) {
+  int sum = 0;
+  sum += __builtin_arm_ldrexd(addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return sum;
+}
+
+int test_strexd(char *addr) {
+  int res = 0;
+  res |= __builtin_arm_strexd(4, addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return res;
+}
diff --git a/clang/test/Sema/builtins-arm-exclusive-none.c b/clang/test/Sema/builtins-arm-exclusive-none.c
index 76d327f0111c3..2ef910dd99aaf 100644
--- a/clang/test/Sema/builtins-arm-exclusive-none.c
+++ b/clang/test/Sema/builtins-arm-exclusive-none.c
@@ -1,7 +1,7 @@
 // RUN: %clang_cc1 -triple armv6m -fsyntax-only -verify %s
 
 // Armv6-M does not support exclusive loads/stores at all, so all uses of
-// __builtin_arm_ldrex and __builtin_arm_strex is forbidden.
+// __builtin_arm_ldrex[d] and __builtin_arm_strex[d] is forbidden.
 
 int test_ldrex(char *addr) {
   int sum = 0;
@@ -20,3 +20,21 @@ int test_strex(char *addr) {
   res |= __builtin_arm_strex(42, (long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
   return res;
 }
+
+int test_ldrexd(char *addr) {
+  int sum = 0;
+  sum += __builtin_arm_ldrexd(addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return sum;
+}
+
+int test_strexd(char *addr) {
+  int res = 0;
+  res |= __builtin_arm_strexd(4, addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return res;
+}
diff --git a/clang/test/Sema/builtins-arm-exclusive.c b/clang/test/Sema/builtins-arm-exclusive.c
index 49aea1506f25c..dbb3de51be419 100644
--- a/clang/test/Sema/builtins-arm-exclusive.c
+++ b/clang/test/Sema/builtins-arm-exclusive.c
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -triple armv7 -fsyntax-only -verify %s
 
-// General tests of __builtin_arm_ldrex and __builtin_arm_strex error checking.
+// General tests of __builtin_arm_ldrex[d] and __builtin_arm_strex[d] error checking.
 //
 // This test is compiled for Armv7-A, which provides exclusive load/store
 // instructions for 1-, 2-, 4- and 8-byte quantities. Other Arm architecture
@@ -63,6 +63,57 @@ int test_strex(char *addr) {
   return res;
 }
 
+int test_ldrexd(char *addr) {
+  int sum = 0;
+  sum += __builtin_arm_ldrexd(addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((short *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((int *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((long long *)addr);
+  sum += __builtin_arm_ldrexd((float *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((double *)addr);
+  sum += *__builtin_arm_ldrexd((int **)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((struct Simple **)addr)->a; // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((volatile char *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((const volatile char *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+  // In principle this might be valid, but stick to ints and floats for scalar
+  // types at the moment.
+  sum += __builtin_arm_ldrexd((struct Simple *)addr).a; // expected-error {{address argument to atomic builtin must be a pointer to}}
+
+  sum += __builtin_arm_ldrexd((__int128 *)addr); // expected-error {{__int128 is not supported on this target}} expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+  __builtin_arm_ldrexd(); // expected-error {{too few arguments to function call}}
+  __builtin_arm_ldrexd(1, 2); // expected-error {{too many arguments to function call}}
+  return sum;
+}
+
+int test_strexd(char *addr) {
+  int res = 0;
+  struct Simple var = {0};
+  res |= __builtin_arm_strexd(4, addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(42, (short *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(42, (int *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(42, (long long *)addr);
+  res |= __builtin_arm_strexd(2.71828f, (float *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(3.14159, (double *)addr);
+  res |= __builtin_arm_strexd(&var, (struct Simple **)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+  res |= __builtin_arm_strexd(42, (volatile char *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(42, (char *const)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(42, (const char *)addr); // expected-warning {{passing 'const char *' to parameter of type 'volatile char *' discards qualifiers}} expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+
+  res |= __builtin_arm_strexd(var, (struct Simple *)addr); // expected-error {{address argument to atomic builtin must be a pointer to}}
+  res |= __builtin_arm_strexd(var, (struct Simple **)addr); // expected-error {{passing 'struct Simple' to parameter of incompatible type 'struct Simple *'}} expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(&var, (struct Simple **)addr).a; // expected-error {{is not a structure or union}} expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+  res |= __builtin_arm_strexd(1, (__int128 *)addr); // expected-error {{__int128 is not supported on this target}} expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+  __builtin_arm_strexd(1); // expected-error {{too few arguments to function call}}
+  __builtin_arm_strexd(1, 2, 3); // expected-error {{too many arguments to function call}}
+  return res;
+}
+
 int test_ldaex(char *addr) {
   int sum = 0;
   sum += __builtin_arm_ldaex(addr);

@llvmbot
Copy link
Member

llvmbot commented Oct 24, 2025

@llvm/pr-subscribers-backend-arm

Author: Douglas (dgg5503)

Changes

This change enables validation checks against the following two ARM atomic builtins:

__builtin_arm_ldrexd
__builtin_arm_strexd

Previously, no checks existed for these builtins, so under a release compiler, it would be possible to emit ldrexd/strexd under ARM targets which set the LDREX mask (returned via getARMLDREXMask) to signify these as unsupported instructions.

For example, the following would compile with errors:

> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strex(42, &num);
    return __builtin_arm_ldrex(&num);
}
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('volatile long long *' invalid)
    3 |     __builtin_arm_strex(42, &num);
      |     ^
atomics.c:4:12: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('const volatile long long *' invalid)
    4 |     return __builtin_arm_ldrex(&num);
      |            ^
2 errors generated.

However, a similar program would compile without errors:

> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strexd(42, &num);
    return __builtin_arm_ldrexd(&num);
}
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
...
        strexd  r1, r2, r3, [r0]
        ldrexd  r0, r1, [r0]
...

With this change, we now have appropriate compile-time errors:

> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: load and store exclusive builtins are not available on this architecture
    3 |     __builtin_arm_strexd(42, &num);
      |     ^                        ~~~~
atomics.c:4:12: error: load and store exclusive builtins are not available on this architecture
    4 |     return __builtin_arm_ldrexd(&num);
      |            ^                    ~~~~
2 errors generated.

If possible, it might be better to remove
__builtin_arm_ldrexd/__builtin_arm_strexd in favor of the generic versions which seem to already support double-word length. If this is preferred, I'd be happy to close this pull-request and open a new one with the intrinsics removed.


Full diff: https://github.com/llvm/llvm-project/pull/164919.diff

8 Files Affected:

  • (modified) clang/include/clang/Basic/BuiltinsARM.def (+2-2)
  • (modified) clang/lib/Sema/SemaARM.cpp (+12)
  • (modified) clang/test/CodeGen/builtins-arm-exclusive.c (+46)
  • (modified) clang/test/CodeGenCXX/builtins-arm-exclusive.cpp (+32)
  • (modified) clang/test/Sema/builtins-arm-exclusive-124.c (+24)
  • (modified) clang/test/Sema/builtins-arm-exclusive-4.c (+18)
  • (modified) clang/test/Sema/builtins-arm-exclusive-none.c (+19-1)
  • (modified) clang/test/Sema/builtins-arm-exclusive.c (+52-1)
diff --git a/clang/include/clang/Basic/BuiltinsARM.def b/clang/include/clang/Basic/BuiltinsARM.def
index 2592e25e95c37..cdcc0d0d65e20 100644
--- a/clang/include/clang/Basic/BuiltinsARM.def
+++ b/clang/include/clang/Basic/BuiltinsARM.def
@@ -125,8 +125,8 @@ BUILTIN(__builtin_arm_cls, "UiZUi", "nc")
 BUILTIN(__builtin_arm_cls64, "UiWUi", "nc")
 
 // Store and load exclusive
-BUILTIN(__builtin_arm_ldrexd, "LLUiv*", "")
-BUILTIN(__builtin_arm_strexd, "iLLUiv*", "")
+BUILTIN(__builtin_arm_ldrexd, "v.", "t")
+BUILTIN(__builtin_arm_strexd, "i.", "t")
 
 BUILTIN(__builtin_arm_ldrex, "v.", "t")
 BUILTIN(__builtin_arm_ldaex, "v.", "t")
diff --git a/clang/lib/Sema/SemaARM.cpp b/clang/lib/Sema/SemaARM.cpp
index 1c7c832d7edfa..20cfa09754384 100644
--- a/clang/lib/Sema/SemaARM.cpp
+++ b/clang/lib/Sema/SemaARM.cpp
@@ -850,8 +850,10 @@ bool SemaARM::CheckARMBuiltinExclusiveCall(const TargetInfo &TI,
                                            unsigned BuiltinID,
                                            CallExpr *TheCall) {
   assert((BuiltinID == ARM::BI__builtin_arm_ldrex ||
+          BuiltinID == ARM::BI__builtin_arm_ldrexd ||
           BuiltinID == ARM::BI__builtin_arm_ldaex ||
           BuiltinID == ARM::BI__builtin_arm_strex ||
+          BuiltinID == ARM::BI__builtin_arm_strexd ||
           BuiltinID == ARM::BI__builtin_arm_stlex ||
           BuiltinID == AArch64::BI__builtin_arm_ldrex ||
           BuiltinID == AArch64::BI__builtin_arm_ldaex ||
@@ -859,9 +861,12 @@ bool SemaARM::CheckARMBuiltinExclusiveCall(const TargetInfo &TI,
           BuiltinID == AArch64::BI__builtin_arm_stlex) &&
          "unexpected ARM builtin");
   bool IsLdrex = BuiltinID == ARM::BI__builtin_arm_ldrex ||
+                 BuiltinID == ARM::BI__builtin_arm_ldrexd ||
                  BuiltinID == ARM::BI__builtin_arm_ldaex ||
                  BuiltinID == AArch64::BI__builtin_arm_ldrex ||
                  BuiltinID == AArch64::BI__builtin_arm_ldaex;
+  bool IsDoubleWord = BuiltinID == ARM::BI__builtin_arm_ldrexd ||
+                      BuiltinID == ARM::BI__builtin_arm_strexd;
 
   ASTContext &Context = getASTContext();
   DeclRefExpr *DRE =
@@ -928,6 +933,11 @@ bool SemaARM::CheckARMBuiltinExclusiveCall(const TargetInfo &TI,
   if (!TI.getTriple().isAArch64()) {
     unsigned Mask = TI.getARMLDREXMask();
     unsigned Bits = Context.getTypeSize(ValType);
+    if (IsDoubleWord) {
+      // Explicit request for ldrexd/strexd means only double word sizes
+      // supported if the target supports them.
+      Mask &= TargetInfo::ARM_LDREX_D;
+    }
     bool Supported =
         (llvm::isPowerOf2_64(Bits)) && Bits >= 8 && (Mask & (Bits / 8));
 
@@ -1013,8 +1023,10 @@ bool SemaARM::CheckARMBuiltinFunctionCall(const TargetInfo &TI,
                                           unsigned BuiltinID,
                                           CallExpr *TheCall) {
   if (BuiltinID == ARM::BI__builtin_arm_ldrex ||
+      BuiltinID == ARM::BI__builtin_arm_ldrexd ||
       BuiltinID == ARM::BI__builtin_arm_ldaex ||
       BuiltinID == ARM::BI__builtin_arm_strex ||
+      BuiltinID == ARM::BI__builtin_arm_strexd ||
       BuiltinID == ARM::BI__builtin_arm_stlex) {
     return CheckARMBuiltinExclusiveCall(TI, BuiltinID, TheCall);
   }
diff --git a/clang/test/CodeGen/builtins-arm-exclusive.c b/clang/test/CodeGen/builtins-arm-exclusive.c
index d2aaf26502a3d..f27dcfc81f34b 100644
--- a/clang/test/CodeGen/builtins-arm-exclusive.c
+++ b/clang/test/CodeGen/builtins-arm-exclusive.c
@@ -312,3 +312,49 @@ int test_stlex_128(__int128 *addr, __int128 val) {
 }
 
 #endif
+
+#ifdef __arm__
+// ARM exclusive atomic builtins
+
+int test_ldrexd(char *addr, long long *addr64, float *addrfloat) {
+// CHECK-LABEL: @test_ldrexd
+  int sum = 0;
+  sum += __builtin_arm_ldrexd((long long *)addr);
+// CHECK: call { i32, i32 } @llvm.arm.ldrexd(ptr %addr)
+
+  sum += __builtin_arm_ldrexd(addr64);
+// CHECK: call { i32, i32 } @llvm.arm.ldrexd(ptr %addr64)
+
+  sum += __builtin_arm_ldrexd((double *)addr);
+// CHECK: [[STRUCTRES:%.*]] = call { i32, i32 } @llvm.arm.ldrexd(ptr %addr)
+// CHECK: [[RESHI:%.*]] = extractvalue { i32, i32 } [[STRUCTRES]], 1
+// CHECK: [[RESLO:%.*]] = extractvalue { i32, i32 } [[STRUCTRES]], 0
+// CHECK: [[RESHI64:%.*]] = zext i32 [[RESHI]] to i64
+// CHECK: [[RESLO64:%.*]] = zext i32 [[RESLO]] to i64
+// CHECK: [[RESHIHI:%.*]] = shl nuw i64 [[RESHI64]], 32
+// CHECK: [[INTRES:%.*]] = or i64 [[RESHIHI]], [[RESLO64]]
+
+  return sum;
+}
+
+int test_strexd(char *addr) {
+// CHECK-LABEL: @test_strexd
+  int res = 0;
+  res |= __builtin_arm_strexd(42, (long long *)addr);
+// CHECK: store i64 42, ptr [[TMP:%.*]], align 8
+// CHECK: [[LOHI:%.*]] = load { i32, i32 }, ptr [[TMP]]
+// CHECK: [[LO:%.*]] = extractvalue { i32, i32 } [[LOHI]], 0
+// CHECK: [[HI:%.*]] = extractvalue { i32, i32 } [[LOHI]], 1
+// CHECK: call i32 @llvm.arm.strexd(i32 [[LO]], i32 [[HI]], ptr %addr)
+
+  res |= __builtin_arm_strexd(3.14159, (double *)addr);
+// CHECK: store double 3.141590e+00, ptr [[TMP:%.*]], align 8
+// CHECK: [[LOHI:%.*]] = load { i32, i32 }, ptr [[TMP]]
+// CHECK: [[LO:%.*]] = extractvalue { i32, i32 } [[LOHI]], 0
+// CHECK: [[HI:%.*]] = extractvalue { i32, i32 } [[LOHI]], 1
+// CHECK: call i32 @llvm.arm.strexd(i32 [[LO]], i32 [[HI]], ptr %addr)
+
+  return res;
+}
+
+#endif
diff --git a/clang/test/CodeGenCXX/builtins-arm-exclusive.cpp b/clang/test/CodeGenCXX/builtins-arm-exclusive.cpp
index d30631f6ad3bd..ca271935a90f1 100644
--- a/clang/test/CodeGenCXX/builtins-arm-exclusive.cpp
+++ b/clang/test/CodeGenCXX/builtins-arm-exclusive.cpp
@@ -22,3 +22,35 @@ void test_ldrex() {
 void tset_strex() {
   __builtin_arm_strex(true, &b);
 }
+
+#ifdef __arm__
+// ARM exclusive atomic builtins
+
+long long c;
+
+// CHECK-LABEL: @_Z11test_ldrexdv()
+// CHECK: [[STRUCTRES:%.*]] = call { i32, i32 } @llvm.arm.ldrexd(ptr @c)
+// CHECK: [[RESHI:%.*]] = extractvalue { i32, i32 } [[STRUCTRES]], 1
+// CHECK: [[RESLO:%.*]] = extractvalue { i32, i32 } [[STRUCTRES]], 0
+// CHECK: [[RESHI64:%.*]] = zext i32 [[RESHI]] to i64
+// CHECK: [[RESLO64:%.*]] = zext i32 [[RESLO]] to i64
+// CHECK: [[RESHIHI:%.*]] = shl nuw i64 [[RESHI64]], 32
+// CHECK: [[INTRES:%.*]] = or i64 [[RESHIHI]], [[RESLO64]]
+// CHECK: store i64 [[INTRES]], ptr @c, align 8
+
+void test_ldrexd() {
+  c = __builtin_arm_ldrexd(&c);
+}
+
+// CHECK-LABEL: @_Z11tset_strexdv()
+// CHECK: store i64 42, ptr [[TMP:%.*]], align 8
+// CHECK: [[LOHI:%.*]] = load { i32, i32 }, ptr [[TMP]]
+// CHECK: [[LO:%.*]] = extractvalue { i32, i32 } [[LOHI]], 0
+// CHECK: [[HI:%.*]] = extractvalue { i32, i32 } [[LOHI]], 1
+// CHECK: %{{.*}} = call i32 @llvm.arm.strexd(i32 [[LO]], i32 [[HI]], ptr @c)
+
+void tset_strexd() {
+  __builtin_arm_strexd(42, &c);
+}
+
+#endif
diff --git a/clang/test/Sema/builtins-arm-exclusive-124.c b/clang/test/Sema/builtins-arm-exclusive-124.c
index 013ae3f41ee7f..1d7d85a5a7416 100644
--- a/clang/test/Sema/builtins-arm-exclusive-124.c
+++ b/clang/test/Sema/builtins-arm-exclusive-124.c
@@ -24,3 +24,27 @@ int test_strex(char *addr) {
   res |= __builtin_arm_strex(42, (long long *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 1,2 or 4 byte type}}
   return res;
 }
+
+int test_ldrexd(char *addr) {
+  int sum = 0;
+  sum += __builtin_arm_ldrexd(addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((unsigned long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((float *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((double *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return sum;
+}
+
+int test_strexd(char *addr) {
+  int res = 0;
+  res |= __builtin_arm_strexd(4, addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (unsigned long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(2.71828f, (float *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(3.14159, (double *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return res;
+}
diff --git a/clang/test/Sema/builtins-arm-exclusive-4.c b/clang/test/Sema/builtins-arm-exclusive-4.c
index 68f01f5416616..0d31ce6f03769 100644
--- a/clang/test/Sema/builtins-arm-exclusive-4.c
+++ b/clang/test/Sema/builtins-arm-exclusive-4.c
@@ -20,3 +20,21 @@ int test_strex(char *addr) {
   res |= __builtin_arm_strex(42, (long long *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 4 byte type}}
   return res;
 }
+
+int test_ldrexd(char *addr) {
+  int sum = 0;
+  sum += __builtin_arm_ldrexd(addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return sum;
+}
+
+int test_strexd(char *addr) {
+  int res = 0;
+  res |= __builtin_arm_strexd(4, addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return res;
+}
diff --git a/clang/test/Sema/builtins-arm-exclusive-none.c b/clang/test/Sema/builtins-arm-exclusive-none.c
index 76d327f0111c3..2ef910dd99aaf 100644
--- a/clang/test/Sema/builtins-arm-exclusive-none.c
+++ b/clang/test/Sema/builtins-arm-exclusive-none.c
@@ -1,7 +1,7 @@
 // RUN: %clang_cc1 -triple armv6m -fsyntax-only -verify %s
 
 // Armv6-M does not support exclusive loads/stores at all, so all uses of
-// __builtin_arm_ldrex and __builtin_arm_strex is forbidden.
+// __builtin_arm_ldrex[d] and __builtin_arm_strex[d] is forbidden.
 
 int test_ldrex(char *addr) {
   int sum = 0;
@@ -20,3 +20,21 @@ int test_strex(char *addr) {
   res |= __builtin_arm_strex(42, (long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
   return res;
 }
+
+int test_ldrexd(char *addr) {
+  int sum = 0;
+  sum += __builtin_arm_ldrexd(addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  sum += __builtin_arm_ldrexd((long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return sum;
+}
+
+int test_strexd(char *addr) {
+  int res = 0;
+  res |= __builtin_arm_strexd(4, addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  res |= __builtin_arm_strexd(42, (long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
+  return res;
+}
diff --git a/clang/test/Sema/builtins-arm-exclusive.c b/clang/test/Sema/builtins-arm-exclusive.c
index 49aea1506f25c..dbb3de51be419 100644
--- a/clang/test/Sema/builtins-arm-exclusive.c
+++ b/clang/test/Sema/builtins-arm-exclusive.c
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -triple armv7 -fsyntax-only -verify %s
 
-// General tests of __builtin_arm_ldrex and __builtin_arm_strex error checking.
+// General tests of __builtin_arm_ldrex[d] and __builtin_arm_strex[d] error checking.
 //
 // This test is compiled for Armv7-A, which provides exclusive load/store
 // instructions for 1-, 2-, 4- and 8-byte quantities. Other Arm architecture
@@ -63,6 +63,57 @@ int test_strex(char *addr) {
   return res;
 }
 
+int test_ldrexd(char *addr) {
+  int sum = 0;
+  sum += __builtin_arm_ldrexd(addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((short *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((int *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((long long *)addr);
+  sum += __builtin_arm_ldrexd((float *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((double *)addr);
+  sum += *__builtin_arm_ldrexd((int **)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((struct Simple **)addr)->a; // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((volatile char *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  sum += __builtin_arm_ldrexd((const volatile char *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+  // In principle this might be valid, but stick to ints and floats for scalar
+  // types at the moment.
+  sum += __builtin_arm_ldrexd((struct Simple *)addr).a; // expected-error {{address argument to atomic builtin must be a pointer to}}
+
+  sum += __builtin_arm_ldrexd((__int128 *)addr); // expected-error {{__int128 is not supported on this target}} expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+  __builtin_arm_ldrexd(); // expected-error {{too few arguments to function call}}
+  __builtin_arm_ldrexd(1, 2); // expected-error {{too many arguments to function call}}
+  return sum;
+}
+
+int test_strexd(char *addr) {
+  int res = 0;
+  struct Simple var = {0};
+  res |= __builtin_arm_strexd(4, addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(42, (short *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(42, (int *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(42, (long long *)addr);
+  res |= __builtin_arm_strexd(2.71828f, (float *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(3.14159, (double *)addr);
+  res |= __builtin_arm_strexd(&var, (struct Simple **)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+  res |= __builtin_arm_strexd(42, (volatile char *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(42, (char *const)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(42, (const char *)addr); // expected-warning {{passing 'const char *' to parameter of type 'volatile char *' discards qualifiers}} expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+
+  res |= __builtin_arm_strexd(var, (struct Simple *)addr); // expected-error {{address argument to atomic builtin must be a pointer to}}
+  res |= __builtin_arm_strexd(var, (struct Simple **)addr); // expected-error {{passing 'struct Simple' to parameter of incompatible type 'struct Simple *'}} expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+  res |= __builtin_arm_strexd(&var, (struct Simple **)addr).a; // expected-error {{is not a structure or union}} expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+  res |= __builtin_arm_strexd(1, (__int128 *)addr); // expected-error {{__int128 is not supported on this target}} expected-error {{address argument to load or store exclusive builtin must be a pointer to 8 byte type}}
+
+  __builtin_arm_strexd(1); // expected-error {{too few arguments to function call}}
+  __builtin_arm_strexd(1, 2, 3); // expected-error {{too many arguments to function call}}
+  return res;
+}
+
 int test_ldaex(char *addr) {
   int sum = 0;
   sum += __builtin_arm_ldaex(addr);

@AZero13
Copy link
Contributor

AZero13 commented Oct 24, 2025

This change enables validation checks against the following two ARM atomic builtins:

__builtin_arm_ldrexd
__builtin_arm_strexd

Previously, no checks existed for these builtins, so under a release compiler, it would be possible to emit ldrexd/strexd under ARM targets which set the LDREX mask (returned via getARMLDREXMask) to signify these as unsupported instructions.

For example, the following would compile with errors:

> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strex(42, &num);
    return __builtin_arm_ldrex(&num);
}
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('volatile long long *' invalid)
    3 |     __builtin_arm_strex(42, &num);
      |     ^
atomics.c:4:12: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('const volatile long long *' invalid)
    4 |     return __builtin_arm_ldrex(&num);
      |            ^
2 errors generated.

However, a similar program would compile without errors:

> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strexd(42, &num);
    return __builtin_arm_ldrexd(&num);
}
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
...
        strexd  r1, r2, r3, [r0]
        ldrexd  r0, r1, [r0]
...

With this change, we now have appropriate compile-time errors:

> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: load and store exclusive builtins are not available on this architecture
    3 |     __builtin_arm_strexd(42, &num);
      |     ^                        ~~~~
atomics.c:4:12: error: load and store exclusive builtins are not available on this architecture
    4 |     return __builtin_arm_ldrexd(&num);
      |            ^                    ~~~~
2 errors generated.

If possible, it might be better to remove __builtin_arm_ldrexd/__builtin_arm_strexd in favor of the generic versions which seem to already support double-word length. If this is preferred, I'd be happy to close this pull-request and open a new one with the intrinsics removed.

Guessing aarch64 doesn't have this problem?

@dgg5503
Copy link
Contributor Author

dgg5503 commented Oct 24, 2025

Hi @AZero13 ,

These builtin functions (__builtin_arm_ldrexd/__builtin_arm_strexd) only exists under ARM at:

BUILTIN(__builtin_arm_ldrexd, "LLUiv*", "")
BUILTIN(__builtin_arm_strexd, "iLLUiv*", "")

I do not see them listed in BuiltinsAArch64.def, so I don't think this problem would occur under AArch64.

Copy link
Collaborator

@statham-arm statham-arm left a comment

Choose a reason for hiding this comment

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

Mostly LGTM, with only one tiny error-reporting quibble.


int test_ldrexd(char *addr) {
int sum = 0;
sum += __builtin_arm_ldrexd(addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's a shame that this case reuses the error message that claims no load/store exclusives are available. I think it might be better to make a separate error message that adds "8-byte" to the wording, and use that when your reduced Mask is zero but the original getARMLDREXMask() was not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @statham-arm

Thanks for the review! I've implemented a tweak to the diagnostic as suggested.

However, attempting to make the diagnostic "%select{|8-byte }0load and store exclusive builtins are not available on this architecture" results in the following error:

error: Diagnostics should not start with a capital letter; '8' is invalid
def err_atomic_exclusive_builtin_pointer_size_none : Error<
    ^

So I've opted for the word 'eight', is that acceptable? Maybe it could be 'double-word' if spelling out a number is too awkward.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Wow, that's surely a bug in Tablegen!

I especially like the way that the complaint "Diagnostics should not start with a capital letter" starts with a capital letter 🙂

I'm happy with 'eight-byte'. It might not be worded exactly the same as the other messages, but it conveys the important information.

@dgg5503 dgg5503 merged commit 6bf6bab into llvm:main Oct 27, 2025
11 checks passed
@dgg5503 dgg5503 deleted the dgg5503/main/arm-builtin-ldrexd-strexd-diagnostics branch October 27, 2025 15:03
dvbuka pushed a commit to dvbuka/llvm-project that referenced this pull request Oct 27, 2025
…m#164919)

This change enables validation checks against the following two ARM
atomic builtins:
```
__builtin_arm_ldrexd
__builtin_arm_strexd
```

Previously, no checks existed for these builtins, so under a release
compiler, it would be possible to emit `ldrexd`/`strexd` under ARM
targets which set the LDREX mask (returned via `getARMLDREXMask`) to
signify these as unsupported instructions.

For example, the following would compile with errors:
```c
> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strex(42, &num);
    return __builtin_arm_ldrex(&num);
}
```
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('volatile long long *' invalid)
    3 |     __builtin_arm_strex(42, &num);
      |     ^
atomics.c:4:12: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('const volatile long long *' invalid)
    4 |     return __builtin_arm_ldrex(&num);
      |            ^
2 errors generated.
```

However, a similar program would compile without errors:
```c
> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strexd(42, &num);
    return __builtin_arm_ldrexd(&num);
}
```
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
...
        strexd  r1, r2, r3, [r0]
        ldrexd  r0, r1, [r0]
...
```

With this change, we now have appropriate compile-time errors:
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: load and store exclusive builtins are not available on this architecture
    3 |     __builtin_arm_strexd(42, &num);
      |     ^                        ~~~~
atomics.c:4:12: error: load and store exclusive builtins are not available on this architecture
    4 |     return __builtin_arm_ldrexd(&num);
      |            ^                    ~~~~
2 errors generated.
```
Lukacma pushed a commit to Lukacma/llvm-project that referenced this pull request Oct 29, 2025
…m#164919)

This change enables validation checks against the following two ARM
atomic builtins:
```
__builtin_arm_ldrexd
__builtin_arm_strexd
```

Previously, no checks existed for these builtins, so under a release
compiler, it would be possible to emit `ldrexd`/`strexd` under ARM
targets which set the LDREX mask (returned via `getARMLDREXMask`) to
signify these as unsupported instructions.

For example, the following would compile with errors:
```c
> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strex(42, &num);
    return __builtin_arm_ldrex(&num);
}
```
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('volatile long long *' invalid)
    3 |     __builtin_arm_strex(42, &num);
      |     ^
atomics.c:4:12: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('const volatile long long *' invalid)
    4 |     return __builtin_arm_ldrex(&num);
      |            ^
2 errors generated.
```

However, a similar program would compile without errors:
```c
> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strexd(42, &num);
    return __builtin_arm_ldrexd(&num);
}
```
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
...
        strexd  r1, r2, r3, [r0]
        ldrexd  r0, r1, [r0]
...
```

With this change, we now have appropriate compile-time errors:
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: load and store exclusive builtins are not available on this architecture
    3 |     __builtin_arm_strexd(42, &num);
      |     ^                        ~~~~
atomics.c:4:12: error: load and store exclusive builtins are not available on this architecture
    4 |     return __builtin_arm_ldrexd(&num);
      |            ^                    ~~~~
2 errors generated.
```
aokblast pushed a commit to aokblast/llvm-project that referenced this pull request Oct 30, 2025
…m#164919)

This change enables validation checks against the following two ARM
atomic builtins:
```
__builtin_arm_ldrexd
__builtin_arm_strexd
```

Previously, no checks existed for these builtins, so under a release
compiler, it would be possible to emit `ldrexd`/`strexd` under ARM
targets which set the LDREX mask (returned via `getARMLDREXMask`) to
signify these as unsupported instructions.

For example, the following would compile with errors:
```c
> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strex(42, &num);
    return __builtin_arm_ldrex(&num);
}
```
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('volatile long long *' invalid)
    3 |     __builtin_arm_strex(42, &num);
      |     ^
atomics.c:4:12: error: address argument to load or store exclusive builtin must be a pointer to 1,2
      or 4 byte type ('const volatile long long *' invalid)
    4 |     return __builtin_arm_ldrex(&num);
      |            ^
2 errors generated.
```

However, a similar program would compile without errors:
```c
> type atomics.c
long long func(void) {
    long long num = 0;
    __builtin_arm_strexd(42, &num);
    return __builtin_arm_ldrexd(&num);
}
```
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
...
        strexd  r1, r2, r3, [r0]
        ldrexd  r0, r1, [r0]
...
```

With this change, we now have appropriate compile-time errors:
```
> clang --target=armv7m-linux-gnueabi -S atomics.c -o -
atomics.c:3:5: error: load and store exclusive builtins are not available on this architecture
    3 |     __builtin_arm_strexd(42, &num);
      |     ^                        ~~~~
atomics.c:4:12: error: load and store exclusive builtins are not available on this architecture
    4 |     return __builtin_arm_ldrexd(&num);
      |            ^                    ~~~~
2 errors generated.
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:ARM clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants