[flang-commits] [clang] [flang] [libc] [lldb] [llvm] [libc][stdio] Add support for %m modifier in scanf (PR #218310)

via flang-commits flang-commits at lists.llvm.org
Sun Aug 23 20:51:49 PDT 2026


https://github.com/afnrow created https://github.com/llvm/llvm-project/pull/218310

[libc][stdio] Add support for the %m modifier in scanf

Add support for the %m modifier and it's derivatives as per
POSIX 2008.1 by leveraging the preexisting FormatFlags::Allocate
and allocating 32 bytes at a time that scale by 2x on each iteration
till it reaches the desired outcome.


>From fac08a662a3b6892925908cd53dc2983cfa76e7d Mon Sep 17 00:00:00 2001
From: yahia ahmed <yahia.a.abdrabou at gmail.com>
Date: Wed, 5 Aug 2026 23:51:15 +0100
Subject: [PATCH 01/18] [libc] Compare words in inline_strcmp to compare 8
 bytes at a time

---
 libc/src/string/memory_utils/inline_strcmp.h | 82 +++++++++++++++++---
 1 file changed, 71 insertions(+), 11 deletions(-)

diff --git a/libc/src/string/memory_utils/inline_strcmp.h b/libc/src/string/memory_utils/inline_strcmp.h
index 6758e79ae9ca3..d7dc4cefdfbfe 100644
--- a/libc/src/string/memory_utils/inline_strcmp.h
+++ b/libc/src/string/memory_utils/inline_strcmp.h
@@ -9,20 +9,51 @@
 #ifndef LLVM_LIBC_SRC_STRING_MEMORY_UTILS_INLINE_STRCMP_H
 #define LLVM_LIBC_SRC_STRING_MEMORY_UTILS_INLINE_STRCMP_H
 
-#include "src/__support/macros/attributes.h" // LIBC_INLINE
-#include "src/__support/macros/config.h"     // LIBC_NAMESPACE_DECL
+#include "src/__support/macros/attributes.h"   // LIBC_INLINE
+#include "src/__support/macros/config.h"       // LIBC_NAMESPACE_DECL
+#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
 #include <stddef.h>
+#include <stdint.h>
 
 namespace LIBC_NAMESPACE_DECL {
 
+LIBC_INLINE uint64_t is_null_terminated(uint64_t v) {
+  return (v - 0x0101010101010101ULL) & ~v & 0x8080808080808080ULL;
+}
+
+LIBC_INLINE uint64_t load(const char *ptr) {
+  uint64_t val{0};
+  __builtin_memcpy(&val, ptr, sizeof(uint64_t));
+  return val;
+}
+
 template <typename Comp>
 LIBC_INLINE constexpr int inline_strcmp(const char *left, const char *right,
                                         Comp &&comp) {
-  // TODO: Look at benefits for comparing words at a time.
-  for (; *left && !comp(*left, *right); ++left, ++right)
-    ;
-  return comp(*reinterpret_cast<const unsigned char *>(left),
-              *reinterpret_cast<const unsigned char *>(right));
+  // Page boundry check fallback to generic version
+  if (LIBC_UNLIKELY((reinterpret_cast<uintptr_t>(left) & 4095) > 4088 ||
+                    (reinterpret_cast<uintptr_t>(right) & 4095) > 4088)) {
+    for (; *left && !comp(*left, *right); ++left, ++right)
+      ;
+    return comp(static_cast<unsigned char>(*left),
+                static_cast<unsigned char>(*right));
+  }
+  while (1) {
+    uint64_t val1 = load(left);
+    uint64_t val2 = load(right);
+    uint64_t diff = val1 ^ val2;
+    uint64_t null_mask = is_null_terminated(val1);
+    // Check for character mismatch or null terminator
+    uint64_t zero_or_diff = diff | null_mask;
+    if (zero_or_diff != 0) {
+      size_t byte_pos = __builtin_ctzll(zero_or_diff) >> 3;
+      unsigned char c1 = static_cast<unsigned char>(left[byte_pos]);
+      unsigned char c2 = static_cast<unsigned char>(right[byte_pos]);
+      return comp(c1, c2);
+    }
+    left += 8;
+    right += 8;
+  }
 }
 
 template <typename Comp>
@@ -31,14 +62,43 @@ LIBC_INLINE constexpr int inline_strncmp(const char *left, const char *right,
   if (n == 0)
     return 0;
 
-  // TODO: Look at benefits for comparing words at a time.
-  for (; n > 1; --n, ++left, ++right) {
+  if (LIBC_UNLIKELY((reinterpret_cast<uintptr_t>(left) & 4095) > 4088 ||
+                    (reinterpret_cast<uintptr_t>(right) & 4095) > 4088)) {
+    for (; n > 1; --n, ++left, ++right) {
+      char lc = *left;
+      if (!comp(lc, '\0') || comp(lc, *right))
+        break;
+    }
+    return comp(static_cast<unsigned char>(*left),
+                static_cast<unsigned char>(*right));
+  }
+
+  for (; n >= 8; n -= 8, left += 8, right += 8) {
+    uint64_t val1 = load(left);
+    uint64_t val2 = load(right);
+    uint64_t diff = val1 ^ val2;
+    uint64_t null_mask = is_null_terminated(val1);
+
+    uint64_t zero_or_diff = diff | null_mask;
+    if (zero_or_diff != 0) {
+      size_t byte_pos = __builtin_ctzll(zero_or_diff) >> 3;
+      // If the difference happens past 'n' remaining bytes, they are equal up
+      // to n
+      if (byte_pos >= n)
+        return 0;
+      unsigned char c1 = static_cast<unsigned char>(left[byte_pos]);
+      unsigned char c2 = static_cast<unsigned char>(right[byte_pos]);
+      return comp(c1, c2);
+    }
+  }
+  // Handle remaining 8 bytes if not found in the first loop
+  for (; n > 1; n--, ++left, ++right) {
     char lc = *left;
     if (!comp(lc, '\0') || comp(lc, *right))
       break;
   }
-  return comp(*reinterpret_cast<const unsigned char *>(left),
-              *reinterpret_cast<const unsigned char *>(right));
+  return comp(static_cast<unsigned char>(*left),
+              static_cast<unsigned char>(*right));
 }
 
 } // namespace LIBC_NAMESPACE_DECL

>From b261b71c5019ef2e46cb10d2b304ed90a8b6a56f Mon Sep 17 00:00:00 2001
From: yahia ahmed <yahia.a.abdrabou at gmail.com>
Date: Fri, 7 Aug 2026 01:11:26 +0100
Subject: [PATCH 02/18] Address review comments

---
 libc/src/string/memory_utils/inline_strcmp.h | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/libc/src/string/memory_utils/inline_strcmp.h b/libc/src/string/memory_utils/inline_strcmp.h
index d7dc4cefdfbfe..bec323c98eca6 100644
--- a/libc/src/string/memory_utils/inline_strcmp.h
+++ b/libc/src/string/memory_utils/inline_strcmp.h
@@ -17,6 +17,9 @@
 
 namespace LIBC_NAMESPACE_DECL {
 
+constexpr int PAGE_MASK = 4095;
+constexpr int PAGE_SAFE_OFFSET = 4088;
+
 LIBC_INLINE uint64_t is_null_terminated(uint64_t v) {
   return (v - 0x0101010101010101ULL) & ~v & 0x8080808080808080ULL;
 }
@@ -31,8 +34,10 @@ template <typename Comp>
 LIBC_INLINE constexpr int inline_strcmp(const char *left, const char *right,
                                         Comp &&comp) {
   // Page boundry check fallback to generic version
-  if (LIBC_UNLIKELY((reinterpret_cast<uintptr_t>(left) & 4095) > 4088 ||
-                    (reinterpret_cast<uintptr_t>(right) & 4095) > 4088)) {
+  if (LIBC_UNLIKELY((reinterpret_cast<uintptr_t>(left) & PAGE_MASK) >
+                        PAGE_SAFE_OFFSET ||
+                    (reinterpret_cast<uintptr_t>(right) & PAGE_MASK) >
+                        PAGE_SAFE_OFFSET)) {
     for (; *left && !comp(*left, *right); ++left, ++right)
       ;
     return comp(static_cast<unsigned char>(*left),
@@ -73,7 +78,9 @@ LIBC_INLINE constexpr int inline_strncmp(const char *left, const char *right,
                 static_cast<unsigned char>(*right));
   }
 
-  for (; n >= 8; n -= 8, left += 8, right += 8) {
+  constexpr size_t block_size = sizeof(uint64_t) / 8;
+  for (; n >= block_size;
+       n -= block_size, left += block_size, right += block_size) {
     uint64_t val1 = load(left);
     uint64_t val2 = load(right);
     uint64_t diff = val1 ^ val2;

>From 766e4e637b3cc7ddd90e690d8b651f18a2f58199 Mon Sep 17 00:00:00 2001
From: yahia ahmed <yahia.a.abdrabou at gmail.com>
Date: Tue, 18 Aug 2026 00:45:16 +0300
Subject: [PATCH 03/18] [libc][string] Compare words in inline_strcmp to
 compare 8 bytes at a timeg

---
 .../modules/LLVMLibCCompileOptionRules.cmake  |  1 +
 libc/config/config.json                       |  4 +++
 libc/src/string/memory_utils/inline_strcmp.h  | 31 ++++++++++++-------
 3 files changed, 24 insertions(+), 12 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
index 4f8f3f5d13b82..0821aeaa7584d 100644
--- a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
+++ b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
@@ -119,6 +119,7 @@ function(_get_compile_options_from_config output_var)
     libc_add_definition(config_options "LIBC_QSORT_IMPL=${LIBC_CONF_QSORT_IMPL}")
   endif()
 
+  libc_add_definition(config_options "LIBC_COPT_STRING_COMPARE_IMPL=${LIBC_CONF_STRING_COMPARE_IMPL}")
   libc_add_definition(config_options "LIBC_COPT_STRING_LENGTH_IMPL=${LIBC_CONF_STRING_LENGTH_IMPL}")
   libc_add_definition(config_options "LIBC_COPT_FIND_FIRST_CHARACTER_IMPL=${LIBC_CONF_FIND_FIRST_CHARACTER_IMPL}")
 
diff --git a/libc/config/config.json b/libc/config/config.json
index 01909fb9abf8a..4abaae6bd841f 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -98,6 +98,10 @@
       "value": "element",
       "doc": "Selects the implementation for string-length: 'element', 'word', 'clang_vector', or 'arch_vector'."
     },
+    "LIBC_CONF_STRING_COMPARE_IMPL": {
+      "value": "element",
+      "doc": "Implementation for strcmp/strcmpcase"
+    },
     "LIBC_CONF_FIND_FIRST_CHARACTER_IMPL": {
       "value": "element",
       "doc": "Selects the implementation for find-first-character-related functions: 'element', 'word', 'clang_vector', or 'arch_vector'."
diff --git a/libc/src/string/memory_utils/inline_strcmp.h b/libc/src/string/memory_utils/inline_strcmp.h
index bec323c98eca6..f6252a81841f6 100644
--- a/libc/src/string/memory_utils/inline_strcmp.h
+++ b/libc/src/string/memory_utils/inline_strcmp.h
@@ -19,6 +19,7 @@ namespace LIBC_NAMESPACE_DECL {
 
 constexpr int PAGE_MASK = 4095;
 constexpr int PAGE_SAFE_OFFSET = 4088;
+constexpr int BLOCK_SIZE = sizeof(uint64_t);
 
 LIBC_INLINE uint64_t is_null_terminated(uint64_t v) {
   return (v - 0x0101010101010101ULL) & ~v & 0x8080808080808080ULL;
@@ -34,15 +35,18 @@ template <typename Comp>
 LIBC_INLINE constexpr int inline_strcmp(const char *left, const char *right,
                                         Comp &&comp) {
   // Page boundry check fallback to generic version
+#if defined(LIBC_COPT_STRING_COMPARE_IMPL)
   if (LIBC_UNLIKELY((reinterpret_cast<uintptr_t>(left) & PAGE_MASK) >
                         PAGE_SAFE_OFFSET ||
                     (reinterpret_cast<uintptr_t>(right) & PAGE_MASK) >
                         PAGE_SAFE_OFFSET)) {
+#endif
     for (; *left && !comp(*left, *right); ++left, ++right)
       ;
     return comp(static_cast<unsigned char>(*left),
                 static_cast<unsigned char>(*right));
   }
+#if defined(LIBC_COPT_STRING_COMPARE_IMPL)
   while (1) {
     uint64_t val1 = load(left);
     uint64_t val2 = load(right);
@@ -51,24 +55,28 @@ LIBC_INLINE constexpr int inline_strcmp(const char *left, const char *right,
     // Check for character mismatch or null terminator
     uint64_t zero_or_diff = diff | null_mask;
     if (zero_or_diff != 0) {
-      size_t byte_pos = __builtin_ctzll(zero_or_diff) >> 3;
+      size_t byte_pos = __builtin_ctzll(zero_or_diff) / BLOCK_SIZE;
       unsigned char c1 = static_cast<unsigned char>(left[byte_pos]);
       unsigned char c2 = static_cast<unsigned char>(right[byte_pos]);
       return comp(c1, c2);
     }
-    left += 8;
-    right += 8;
+    left += BLOCK_SIZE;
+    right += BLOCK_SIZE;
   }
 }
+#endif
 
 template <typename Comp>
 LIBC_INLINE constexpr int inline_strncmp(const char *left, const char *right,
                                          size_t n, Comp &&comp) {
   if (n == 0)
     return 0;
-
-  if (LIBC_UNLIKELY((reinterpret_cast<uintptr_t>(left) & 4095) > 4088 ||
-                    (reinterpret_cast<uintptr_t>(right) & 4095) > 4088)) {
+#if defined(LIBC_COPT_STRING_COMPARE_IMPL)
+  if (LIBC_UNLIKELY((reinterpret_cast<uintptr_t>(left) & PAGE_MASK) >
+                        PAGE_SAFE_OFFSET ||
+                    (reinterpret_cast<uintptr_t>(right) & PAGE_MASK) >
+                        PAGE_SAFE_OFFSET)) {
+#endif
     for (; n > 1; --n, ++left, ++right) {
       char lc = *left;
       if (!comp(lc, '\0') || comp(lc, *right))
@@ -77,10 +85,9 @@ LIBC_INLINE constexpr int inline_strncmp(const char *left, const char *right,
     return comp(static_cast<unsigned char>(*left),
                 static_cast<unsigned char>(*right));
   }
-
-  constexpr size_t block_size = sizeof(uint64_t) / 8;
-  for (; n >= block_size;
-       n -= block_size, left += block_size, right += block_size) {
+#if defined(LIBC_COPT_STRING_COMPARE_IMPL)
+  for (; n >= BLOCK_SIZE;
+       n -= BLOCK_SIZE, left += BLOCK_SIZE, right += BLOCK_SIZE) {
     uint64_t val1 = load(left);
     uint64_t val2 = load(right);
     uint64_t diff = val1 ^ val2;
@@ -88,7 +95,7 @@ LIBC_INLINE constexpr int inline_strncmp(const char *left, const char *right,
 
     uint64_t zero_or_diff = diff | null_mask;
     if (zero_or_diff != 0) {
-      size_t byte_pos = __builtin_ctzll(zero_or_diff) >> 3;
+      size_t byte_pos = __builtin_ctzll(zero_or_diff) / BLOCK_SIZE;
       // If the difference happens past 'n' remaining bytes, they are equal up
       // to n
       if (byte_pos >= n)
@@ -107,7 +114,7 @@ LIBC_INLINE constexpr int inline_strncmp(const char *left, const char *right,
   return comp(static_cast<unsigned char>(*left),
               static_cast<unsigned char>(*right));
 }
-
+#endif
 } // namespace LIBC_NAMESPACE_DECL
 
 #endif // LLVM_LIBC_SRC_STRING_MEMORY_UTILS_INLINE_STRCMP_H

>From 3d82dd0f0d327019d73d5970d6b0043533f7ca34 Mon Sep 17 00:00:00 2001
From: Krzysztof Drewniak <Krzysztof.Drewniak at amd.com>
Date: Wed, 5 Aug 2026 13:34:00 -0700
Subject: [PATCH 04/18] [NFC] Pre-commit tests for missing fabs SDAG vector
 expansion (#214288)

---
 llvm/test/CodeGen/AMDGPU/fabs-vector-truncate.ll | 9 +++++++++
 1 file changed, 9 insertions(+)
 create mode 100644 llvm/test/CodeGen/AMDGPU/fabs-vector-truncate.ll

diff --git a/llvm/test/CodeGen/AMDGPU/fabs-vector-truncate.ll b/llvm/test/CodeGen/AMDGPU/fabs-vector-truncate.ll
new file mode 100644
index 0000000000000..247f9ca917870
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/fabs-vector-truncate.ll
@@ -0,0 +1,9 @@
+; RUN: not --crash llc -mtriple=amdgpu9.50-amd-amdhsa < %s
+
+define amdgpu_kernel void @reduced_fabs_vector_truncate_crash(<2 x float> %0) {
+  %2 = fptrunc <2 x float> %0 to <2 x half>
+  %3 = shufflevector <2 x half> %2, <2 x half> zeroinitializer, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+  %4 = tail call <4 x half> @llvm.fabs.v4f16(<4 x half> %3)
+  store <4 x half> %4, ptr addrspace(1) null, align 8
+  ret void
+}

>From 5f7cfdf3894163e10e8499564e9278906dd0eb47 Mon Sep 17 00:00:00 2001
From: Nick Sarnie <nick.sarnie at intel.com>
Date: Thu, 6 Aug 2026 05:53:08 +0900
Subject: [PATCH 05/18] [offload] Fix unittests on Windows (#214330)

Right now everything fails because it can't find `LLVMOffload.dll`.
We handle this for e2e tests
[here](https://github.com/llvm/llvm-project/blob/main/offload/test/lit.cfg#L212),
but not for the unit tests.

Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>
---
 offload/test/unit/lit.cfg.py      | 5 +++++
 offload/test/unit/lit.site.cfg.in | 1 +
 2 files changed, 6 insertions(+)

diff --git a/offload/test/unit/lit.cfg.py b/offload/test/unit/lit.cfg.py
index 01435e59ae03b..39f159999c3d8 100644
--- a/offload/test/unit/lit.cfg.py
+++ b/offload/test/unit/lit.cfg.py
@@ -33,6 +33,11 @@ def prepend_executable_path(path):
 if config.cuda_path:
     prepend_executable_path(f"{config.cuda_path}{os.path.sep}bin")
 
+# Windows doesn't have rpath so make sure runtime deps for the unittest
+# executable, such as LLVMOffload.dll, are found.
+if config.operating_system == "Windows" and config.library_dir:
+    prepend_executable_path(config.library_dir)
+
 # test_source_root: The root path where tests are located.
 # test_exec_root: The root path where tests should be run.
 config.test_exec_root = config.unittest_dir
diff --git a/offload/test/unit/lit.site.cfg.in b/offload/test/unit/lit.site.cfg.in
index 821503a92327c..b06bd47fcf94d 100644
--- a/offload/test/unit/lit.site.cfg.in
+++ b/offload/test/unit/lit.site.cfg.in
@@ -5,6 +5,7 @@ config.unittest_dir = "@OFFLOAD_UNITTEST_DIR@"
 config.llvm_build_mode = lit_config.substitute("@LLVM_BUILD_MODE@")
 config.bin_llvm_tools_dir = "@LLVM_TOOLS_BINARY_DIR@"
 config.cuda_path = "@CUDA_ROOT@"
+config.operating_system = "@CMAKE_SYSTEM_NAME@"
 
 # Let the main config do the real work.
 lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/unit/lit.cfg.py")

>From d6d3dda37b71a51e5bb72e33b627dae47b4a9ea6 Mon Sep 17 00:00:00 2001
From: Chinmay Deshpande <chdeshpa at amd.com>
Date: Wed, 5 Aug 2026 14:07:20 -0700
Subject: [PATCH 06/18] [AMDGPU][GISel] RegBankLegalize rules for
 s_incperflevel/s_decperflevel (#214327)

---
 llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp  | 2 ++
 llvm/test/CodeGen/AMDGPU/llvm.amdgcn.s.decperflevel.ll | 2 ++
 llvm/test/CodeGen/AMDGPU/llvm.amdgcn.s.incperflevel.ll | 2 ++
 3 files changed, 6 insertions(+)

diff --git a/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp b/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp
index 29c76f69e6418..54379f5397bd8 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp
@@ -1819,6 +1819,8 @@ RegBankLegalizeRules::RegBankLegalizeRules(const GCNSubtarget &_ST,
                     amdgcn_s_barrier_leave,
                     amdgcn_s_barrier_signal,
                     amdgcn_s_barrier_wait,
+                    amdgcn_s_decperflevel,
+                    amdgcn_s_incperflevel,
                     amdgcn_s_monitor_sleep,
                     amdgcn_s_nop,
                     amdgcn_s_sethalt,
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.s.decperflevel.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.s.decperflevel.ll
index d4daa2abacdec..c88faefadcad1 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.s.decperflevel.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.s.decperflevel.ll
@@ -1,5 +1,7 @@
 ; RUN: llc -mtriple=amdgpu6.00 < %s | FileCheck -check-prefix=GCN %s
 ; RUN: llc -mtriple=amdgpu8.02 < %s | FileCheck -check-prefix=GCN %s
+; RUN: llc -global-isel -mtriple=amdgpu6.00 < %s | FileCheck -check-prefix=GCN %s
+; RUN: llc -global-isel -mtriple=amdgpu8.02 < %s | FileCheck -check-prefix=GCN %s
 
 declare void @llvm.amdgcn.s.decperflevel(i32) #0
 
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.s.incperflevel.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.s.incperflevel.ll
index 91d3b4361d613..8e02b7de09b29 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.s.incperflevel.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.s.incperflevel.ll
@@ -1,5 +1,7 @@
 ; RUN: llc -mtriple=amdgpu6.00 < %s | FileCheck -check-prefix=GCN %s
 ; RUN: llc -mtriple=amdgpu8.02 < %s | FileCheck -check-prefix=GCN %s
+; RUN: llc -global-isel -mtriple=amdgpu6.00 < %s | FileCheck -check-prefix=GCN %s
+; RUN: llc -global-isel -mtriple=amdgpu8.02 < %s | FileCheck -check-prefix=GCN %s
 
 declare void @llvm.amdgcn.s.incperflevel(i32) #0
 

>From 37ad08012fef682c8bcaf0ea8d559d1cad13f87c Mon Sep 17 00:00:00 2001
From: Chinmay Deshpande <chdeshpa at amd.com>
Date: Wed, 5 Aug 2026 14:14:14 -0700
Subject: [PATCH 07/18] [AMDGPU][GISel] RegBankLegalize rules for SAD
 intrinsics (#214329)

---
 .../AMDGPU/AMDGPURegBankLegalizeRules.cpp     |   6 +
 .../CodeGen/AMDGPU/llvm.amdgcn.msad.u8.ll     | 152 ++++++++++++++++--
 .../CodeGen/AMDGPU/llvm.amdgcn.sad.hi.u8.ll   | 114 ++++++++++++-
 .../CodeGen/AMDGPU/llvm.amdgcn.sad.u16.ll     | 114 ++++++++++++-
 .../test/CodeGen/AMDGPU/llvm.amdgcn.sad.u8.ll | 114 ++++++++++++-
 5 files changed, 471 insertions(+), 29 deletions(-)

diff --git a/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp b/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp
index 54379f5397bd8..5c276147d0047 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp
@@ -1963,6 +1963,12 @@ RegBankLegalizeRules::RegBankLegalizeRules(const GCNSubtarget &_ST,
       .Uni(S32, {{UniInVgprS32}, {IntrId, Vgpr32, Vgpr32, Vgpr32}})
       .Div(S32, {{Vgpr32}, {IntrId, Vgpr32, Vgpr32, Vgpr32}});
 
+  addRulesForIOpcs(
+      {amdgcn_msad_u8, amdgcn_sad_hi_u8, amdgcn_sad_u16, amdgcn_sad_u8},
+      Standard)
+      .Uni(S32, {{UniInVgprS32}, {IntrId, Vgpr32, Vgpr32, Vgpr32}})
+      .Div(S32, {{Vgpr32}, {IntrId, Vgpr32, Vgpr32, Vgpr32}});
+
   addRulesForIOpcs(
       {amdgcn_wave_reduce_add, amdgcn_wave_reduce_and, amdgcn_wave_reduce_fadd,
        amdgcn_wave_reduce_fmax, amdgcn_wave_reduce_fmin,
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.msad.u8.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.msad.u8.ll
index 13828064df8bb..6d3dfe57925af 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.msad.u8.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.msad.u8.ll
@@ -1,27 +1,157 @@
-; RUN: llc -mtriple=amdgpu6.00 < %s | FileCheck -check-prefixes=GCN,GFX600 %s
-; RUN: llc -mtriple=amdgpu8.03 < %s | FileCheck -check-prefixes=GCN,GFX803 %s
-; RUN: llc -mtriple=amdgpu13.10 < %s | FileCheck -check-prefixes=GCN,GFX13 %s
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -global-isel=0 -mtriple=amdgpu6.00 < %s | FileCheck -check-prefixes=GFX600,GFX600-SDAG %s
+; RUN: llc -global-isel=1 -mtriple=amdgpu6.00 < %s | FileCheck -check-prefixes=GFX600,GFX600-GISEL %s
+; RUN: llc -global-isel=0 -mtriple=amdgpu8.03 < %s | FileCheck -check-prefixes=GFX803,GFX803-SDAG %s
+; RUN: llc -global-isel=1 -mtriple=amdgpu8.03 < %s | FileCheck -check-prefixes=GFX803,GFX803-GISEL %s
+; RUN: llc -global-isel=0 -mtriple=amdgpu13.10 < %s | FileCheck -check-prefixes=GFX13,GFX13-SDAG %s
+; RUN: llc -global-isel=1 -mtriple=amdgpu13.10 < %s | FileCheck -check-prefixes=GFX13,GFX13-GISEL %s
 
 declare i32 @llvm.amdgcn.msad.u8(i32, i32, i32) #0
 
-; GCN-LABEL: {{^}}v_msad_u8:
-; GFX600: v_msad_u8 v{{[0-9]+}}, v{{[0-9]+}}, s{{[0-9]+}}, s{{[0-9]+}}
-; GFX803: v_msad_u8 v{{[0-9]+}}, v{{[0-9]+}}, s{{[0-9]+}}, s{{[0-9]+}}
-; GFX13: v_msad_u8 v{{[0-9]+}}, s{{[0-9]+}}, s{{[0-9]+}}, s{{[0-9]+}}
 define amdgpu_kernel void @v_msad_u8(ptr addrspace(1) %out, i32 %src) {
+; GFX600-SDAG-LABEL: v_msad_u8:
+; GFX600-SDAG:       ; %bb.0:
+; GFX600-SDAG-NEXT:    s_load_dword s6, s[4:5], 0xb
+; GFX600-SDAG-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x9
+; GFX600-SDAG-NEXT:    s_movk_i32 s4, 0x64
+; GFX600-SDAG-NEXT:    s_mov_b32 s3, 0xf000
+; GFX600-SDAG-NEXT:    s_mov_b32 s2, -1
+; GFX600-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; GFX600-SDAG-NEXT:    v_mov_b32_e32 v0, s6
+; GFX600-SDAG-NEXT:    v_msad_u8 v0, v0, s4, s4
+; GFX600-SDAG-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; GFX600-SDAG-NEXT:    s_endpgm
+;
+; GFX600-GISEL-LABEL: v_msad_u8:
+; GFX600-GISEL:       ; %bb.0:
+; GFX600-GISEL-NEXT:    s_load_dword s3, s[4:5], 0xb
+; GFX600-GISEL-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x9
+; GFX600-GISEL-NEXT:    v_mov_b32_e32 v0, 0x64
+; GFX600-GISEL-NEXT:    s_mov_b32 s2, -1
+; GFX600-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; GFX600-GISEL-NEXT:    v_msad_u8 v0, s3, v0, v0
+; GFX600-GISEL-NEXT:    s_mov_b32 s3, 0xf000
+; GFX600-GISEL-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; GFX600-GISEL-NEXT:    s_endpgm
+;
+; GFX803-SDAG-LABEL: v_msad_u8:
+; GFX803-SDAG:       ; %bb.0:
+; GFX803-SDAG-NEXT:    s_load_dword s2, s[4:5], 0x2c
+; GFX803-SDAG-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x24
+; GFX803-SDAG-NEXT:    s_movk_i32 s3, 0x64
+; GFX803-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; GFX803-SDAG-NEXT:    v_mov_b32_e32 v0, s2
+; GFX803-SDAG-NEXT:    v_msad_u8 v2, v0, s3, s3
+; GFX803-SDAG-NEXT:    v_mov_b32_e32 v0, s0
+; GFX803-SDAG-NEXT:    v_mov_b32_e32 v1, s1
+; GFX803-SDAG-NEXT:    flat_store_dword v[0:1], v2
+; GFX803-SDAG-NEXT:    s_endpgm
+;
+; GFX803-GISEL-LABEL: v_msad_u8:
+; GFX803-GISEL:       ; %bb.0:
+; GFX803-GISEL-NEXT:    s_load_dword s2, s[4:5], 0x2c
+; GFX803-GISEL-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x24
+; GFX803-GISEL-NEXT:    v_mov_b32_e32 v0, 0x64
+; GFX803-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; GFX803-GISEL-NEXT:    v_msad_u8 v2, s2, v0, v0
+; GFX803-GISEL-NEXT:    v_mov_b32_e32 v0, s0
+; GFX803-GISEL-NEXT:    v_mov_b32_e32 v1, s1
+; GFX803-GISEL-NEXT:    flat_store_dword v[0:1], v2
+; GFX803-GISEL-NEXT:    s_endpgm
+;
+; GFX13-SDAG-LABEL: v_msad_u8:
+; GFX13-SDAG:       ; %bb.0:
+; GFX13-SDAG-NEXT:    s_load_b96 s[0:2], s[4:5], 0x24 nv
+; GFX13-SDAG-NEXT:    s_movk_i32 s3, 0x64
+; GFX13-SDAG-NEXT:    v_mov_b32_e32 v0, 0
+; GFX13-SDAG-NEXT:    s_wait_kmcnt 0x0
+; GFX13-SDAG-NEXT:    v_msad_u8 v1, s2, s3, s3
+; GFX13-SDAG-NEXT:    global_store_b32 v0, v1, s[0:1]
+; GFX13-SDAG-NEXT:    s_endpgm
+;
+; GFX13-GISEL-LABEL: v_msad_u8:
+; GFX13-GISEL:       ; %bb.0:
+; GFX13-GISEL-NEXT:    s_load_b96 s[0:2], s[4:5], 0x24 nv
+; GFX13-GISEL-NEXT:    v_mov_b32_e32 v1, 0
+; GFX13-GISEL-NEXT:    s_wait_kmcnt 0x0
+; GFX13-GISEL-NEXT:    v_msad_u8 v0, s2, 0x64, 0x64
+; GFX13-GISEL-NEXT:    global_store_b32 v1, v0, s[0:1]
+; GFX13-GISEL-NEXT:    s_endpgm
   %result= call i32 @llvm.amdgcn.msad.u8(i32 %src, i32 100, i32 100) #0
   store i32 %result, ptr addrspace(1) %out, align 4
   ret void
 }
 
-; GCN-LABEL: {{^}}v_msad_u8_non_immediate:
-; GFX600: v_msad_u8 v{{[0-9]+}}, s{{[0-9]+}}, v{{[0-9]+}}, v{{[0-9]+}}
-; GFX803: v_msad_u8 v{{[0-9]+}}, s{{[0-9]+}}, v{{[0-9]+}}, v{{[0-9]+}}
-; GFX13: v_msad_u8 v{{[0-9]+}}, s{{[0-9]+}}, s{{[0-9]+}}, v{{[0-9]+}}
 define amdgpu_kernel void @v_msad_u8_non_immediate(ptr addrspace(1) %out, i32 %src, i32 %a, i32 %b) {
+; GFX600-SDAG-LABEL: v_msad_u8_non_immediate:
+; GFX600-SDAG:       ; %bb.0:
+; GFX600-SDAG-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0xb
+; GFX600-SDAG-NEXT:    s_load_dwordx2 s[4:5], s[4:5], 0x9
+; GFX600-SDAG-NEXT:    s_mov_b32 s7, 0xf000
+; GFX600-SDAG-NEXT:    s_mov_b32 s6, -1
+; GFX600-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; GFX600-SDAG-NEXT:    v_mov_b32_e32 v0, s1
+; GFX600-SDAG-NEXT:    v_mov_b32_e32 v1, s2
+; GFX600-SDAG-NEXT:    v_msad_u8 v0, s0, v0, v1
+; GFX600-SDAG-NEXT:    buffer_store_dword v0, off, s[4:7], 0
+; GFX600-SDAG-NEXT:    s_endpgm
+;
+; GFX600-GISEL-LABEL: v_msad_u8_non_immediate:
+; GFX600-GISEL:       ; %bb.0:
+; GFX600-GISEL-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x9
+; GFX600-GISEL-NEXT:    s_load_dword s4, s[4:5], 0xd
+; GFX600-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; GFX600-GISEL-NEXT:    v_mov_b32_e32 v0, s3
+; GFX600-GISEL-NEXT:    v_mov_b32_e32 v1, s4
+; GFX600-GISEL-NEXT:    v_msad_u8 v0, s2, v0, v1
+; GFX600-GISEL-NEXT:    s_mov_b32 s2, -1
+; GFX600-GISEL-NEXT:    s_mov_b32 s3, 0xf000
+; GFX600-GISEL-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; GFX600-GISEL-NEXT:    s_endpgm
+;
+; GFX803-SDAG-LABEL: v_msad_u8_non_immediate:
+; GFX803-SDAG:       ; %bb.0:
+; GFX803-SDAG-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x2c
+; GFX803-SDAG-NEXT:    s_load_dwordx2 s[4:5], s[4:5], 0x24
+; GFX803-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; GFX803-SDAG-NEXT:    v_mov_b32_e32 v0, s1
+; GFX803-SDAG-NEXT:    v_mov_b32_e32 v1, s2
+; GFX803-SDAG-NEXT:    v_msad_u8 v2, s0, v0, v1
+; GFX803-SDAG-NEXT:    v_mov_b32_e32 v0, s4
+; GFX803-SDAG-NEXT:    v_mov_b32_e32 v1, s5
+; GFX803-SDAG-NEXT:    flat_store_dword v[0:1], v2
+; GFX803-SDAG-NEXT:    s_endpgm
+;
+; GFX803-GISEL-LABEL: v_msad_u8_non_immediate:
+; GFX803-GISEL:       ; %bb.0:
+; GFX803-GISEL-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x24
+; GFX803-GISEL-NEXT:    s_load_dword s4, s[4:5], 0x34
+; GFX803-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; GFX803-GISEL-NEXT:    v_mov_b32_e32 v0, s3
+; GFX803-GISEL-NEXT:    v_mov_b32_e32 v1, s4
+; GFX803-GISEL-NEXT:    v_msad_u8 v2, s2, v0, v1
+; GFX803-GISEL-NEXT:    v_mov_b32_e32 v0, s0
+; GFX803-GISEL-NEXT:    v_mov_b32_e32 v1, s1
+; GFX803-GISEL-NEXT:    flat_store_dword v[0:1], v2
+; GFX803-GISEL-NEXT:    s_endpgm
+;
+; GFX13-LABEL: v_msad_u8_non_immediate:
+; GFX13:       ; %bb.0:
+; GFX13-NEXT:    s_clause 0x1
+; GFX13-NEXT:    s_load_b96 s[0:2], s[4:5], 0x2c nv
+; GFX13-NEXT:    s_load_b64 s[4:5], s[4:5], 0x24 nv
+; GFX13-NEXT:    s_wait_kmcnt 0x0
+; GFX13-NEXT:    v_dual_mov_b32 v1, 0 :: v_dual_mov_b32 v0, s2
+; GFX13-NEXT:    s_delay_alu instid0(VALU_DEP_1)
+; GFX13-NEXT:    v_msad_u8 v0, s0, s1, v0
+; GFX13-NEXT:    global_store_b32 v1, v0, s[4:5]
+; GFX13-NEXT:    s_endpgm
   %result= call i32 @llvm.amdgcn.msad.u8(i32 %src, i32 %a, i32 %b) #0
   store i32 %result, ptr addrspace(1) %out, align 4
   ret void
 }
 
 attributes #0 = { nounwind readnone }
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; GFX600: {{.*}}
+; GFX803: {{.*}}
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.hi.u8.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.hi.u8.ll
index 93ba9c69b54bc..c6430f3a90b5d 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.hi.u8.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.hi.u8.ll
@@ -1,22 +1,124 @@
-; RUN: llc -mtriple=amdgpu6.00 < %s | FileCheck -check-prefix=GCN %s
-; RUN: llc -mtriple=amdgpu8.03 < %s | FileCheck -check-prefix=GCN %s
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -global-isel=0 -mtriple=amdgpu6.00 < %s | FileCheck -check-prefixes=SI,SI-SDAG %s
+; RUN: llc -global-isel=1 -mtriple=amdgpu6.00 < %s | FileCheck -check-prefixes=SI,SI-GISEL %s
+; RUN: llc -global-isel=0 -mtriple=amdgpu8.03 < %s | FileCheck -check-prefixes=VI,VI-SDAG %s
+; RUN: llc -global-isel=1 -mtriple=amdgpu8.03 < %s | FileCheck -check-prefixes=VI,VI-GISEL %s
 
 declare i32 @llvm.amdgcn.sad.hi.u8(i32, i32, i32) #0
 
-; GCN-LABEL: {{^}}v_sad_hi_u8:
-; GCN: v_sad_hi_u8 v{{[0-9]+}}, v{{[0-9]+}}, s{{[0-9]+}}, s{{[0-9]+}}
 define amdgpu_kernel void @v_sad_hi_u8(ptr addrspace(1) %out, i32 %src) {
+; SI-SDAG-LABEL: v_sad_hi_u8:
+; SI-SDAG:       ; %bb.0:
+; SI-SDAG-NEXT:    s_load_dword s6, s[4:5], 0xb
+; SI-SDAG-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x9
+; SI-SDAG-NEXT:    s_movk_i32 s4, 0x64
+; SI-SDAG-NEXT:    s_mov_b32 s3, 0xf000
+; SI-SDAG-NEXT:    s_mov_b32 s2, -1
+; SI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-SDAG-NEXT:    v_mov_b32_e32 v0, s6
+; SI-SDAG-NEXT:    v_sad_hi_u8 v0, v0, s4, s4
+; SI-SDAG-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; SI-SDAG-NEXT:    s_endpgm
+;
+; SI-GISEL-LABEL: v_sad_hi_u8:
+; SI-GISEL:       ; %bb.0:
+; SI-GISEL-NEXT:    s_load_dword s3, s[4:5], 0xb
+; SI-GISEL-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x9
+; SI-GISEL-NEXT:    v_mov_b32_e32 v0, 0x64
+; SI-GISEL-NEXT:    s_mov_b32 s2, -1
+; SI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-GISEL-NEXT:    v_sad_hi_u8 v0, s3, v0, v0
+; SI-GISEL-NEXT:    s_mov_b32 s3, 0xf000
+; SI-GISEL-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; SI-GISEL-NEXT:    s_endpgm
+;
+; VI-SDAG-LABEL: v_sad_hi_u8:
+; VI-SDAG:       ; %bb.0:
+; VI-SDAG-NEXT:    s_load_dword s2, s[4:5], 0x2c
+; VI-SDAG-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x24
+; VI-SDAG-NEXT:    s_movk_i32 s3, 0x64
+; VI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s2
+; VI-SDAG-NEXT:    v_sad_hi_u8 v2, v0, s3, s3
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s0
+; VI-SDAG-NEXT:    v_mov_b32_e32 v1, s1
+; VI-SDAG-NEXT:    flat_store_dword v[0:1], v2
+; VI-SDAG-NEXT:    s_endpgm
+;
+; VI-GISEL-LABEL: v_sad_hi_u8:
+; VI-GISEL:       ; %bb.0:
+; VI-GISEL-NEXT:    s_load_dword s2, s[4:5], 0x2c
+; VI-GISEL-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x24
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, 0x64
+; VI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-GISEL-NEXT:    v_sad_hi_u8 v2, s2, v0, v0
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, s0
+; VI-GISEL-NEXT:    v_mov_b32_e32 v1, s1
+; VI-GISEL-NEXT:    flat_store_dword v[0:1], v2
+; VI-GISEL-NEXT:    s_endpgm
   %result= call i32 @llvm.amdgcn.sad.hi.u8(i32 %src, i32 100, i32 100) #0
   store i32 %result, ptr addrspace(1) %out, align 4
   ret void
 }
 
-; GCN-LABEL: {{^}}v_sad_hi_u8_non_immediate:
-; GCN: v_sad_hi_u8 v{{[0-9]+}}, s{{[0-9]+}}, v{{[0-9]+}}, v{{[0-9]+}}
 define amdgpu_kernel void @v_sad_hi_u8_non_immediate(ptr addrspace(1) %out, i32 %src, i32 %a, i32 %b) {
+; SI-SDAG-LABEL: v_sad_hi_u8_non_immediate:
+; SI-SDAG:       ; %bb.0:
+; SI-SDAG-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0xb
+; SI-SDAG-NEXT:    s_load_dwordx2 s[4:5], s[4:5], 0x9
+; SI-SDAG-NEXT:    s_mov_b32 s7, 0xf000
+; SI-SDAG-NEXT:    s_mov_b32 s6, -1
+; SI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-SDAG-NEXT:    v_mov_b32_e32 v0, s1
+; SI-SDAG-NEXT:    v_mov_b32_e32 v1, s2
+; SI-SDAG-NEXT:    v_sad_hi_u8 v0, s0, v0, v1
+; SI-SDAG-NEXT:    buffer_store_dword v0, off, s[4:7], 0
+; SI-SDAG-NEXT:    s_endpgm
+;
+; SI-GISEL-LABEL: v_sad_hi_u8_non_immediate:
+; SI-GISEL:       ; %bb.0:
+; SI-GISEL-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x9
+; SI-GISEL-NEXT:    s_load_dword s4, s[4:5], 0xd
+; SI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-GISEL-NEXT:    v_mov_b32_e32 v0, s3
+; SI-GISEL-NEXT:    v_mov_b32_e32 v1, s4
+; SI-GISEL-NEXT:    v_sad_hi_u8 v0, s2, v0, v1
+; SI-GISEL-NEXT:    s_mov_b32 s2, -1
+; SI-GISEL-NEXT:    s_mov_b32 s3, 0xf000
+; SI-GISEL-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; SI-GISEL-NEXT:    s_endpgm
+;
+; VI-SDAG-LABEL: v_sad_hi_u8_non_immediate:
+; VI-SDAG:       ; %bb.0:
+; VI-SDAG-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x2c
+; VI-SDAG-NEXT:    s_load_dwordx2 s[4:5], s[4:5], 0x24
+; VI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s1
+; VI-SDAG-NEXT:    v_mov_b32_e32 v1, s2
+; VI-SDAG-NEXT:    v_sad_hi_u8 v2, s0, v0, v1
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s4
+; VI-SDAG-NEXT:    v_mov_b32_e32 v1, s5
+; VI-SDAG-NEXT:    flat_store_dword v[0:1], v2
+; VI-SDAG-NEXT:    s_endpgm
+;
+; VI-GISEL-LABEL: v_sad_hi_u8_non_immediate:
+; VI-GISEL:       ; %bb.0:
+; VI-GISEL-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x24
+; VI-GISEL-NEXT:    s_load_dword s4, s[4:5], 0x34
+; VI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, s3
+; VI-GISEL-NEXT:    v_mov_b32_e32 v1, s4
+; VI-GISEL-NEXT:    v_sad_hi_u8 v2, s2, v0, v1
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, s0
+; VI-GISEL-NEXT:    v_mov_b32_e32 v1, s1
+; VI-GISEL-NEXT:    flat_store_dword v[0:1], v2
+; VI-GISEL-NEXT:    s_endpgm
   %result= call i32 @llvm.amdgcn.sad.hi.u8(i32 %src, i32 %a, i32 %b) #0
   store i32 %result, ptr addrspace(1) %out, align 4
   ret void
 }
 
 attributes #0 = { nounwind readnone }
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; SI: {{.*}}
+; VI: {{.*}}
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.u16.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.u16.ll
index d0c9ecafe1262..ec6bdc2d2914e 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.u16.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.u16.ll
@@ -1,22 +1,124 @@
-; RUN: llc -mtriple=amdgpu6.00 < %s | FileCheck -check-prefix=GCN %s
-; RUN: llc -mtriple=amdgpu8.03 < %s | FileCheck -check-prefix=GCN %s
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -global-isel=0 -mtriple=amdgpu6.00 < %s | FileCheck -check-prefixes=SI,SI-SDAG %s
+; RUN: llc -global-isel=1 -mtriple=amdgpu6.00 < %s | FileCheck -check-prefixes=SI,SI-GISEL %s
+; RUN: llc -global-isel=0 -mtriple=amdgpu8.03 < %s | FileCheck -check-prefixes=VI,VI-SDAG %s
+; RUN: llc -global-isel=1 -mtriple=amdgpu8.03 < %s | FileCheck -check-prefixes=VI,VI-GISEL %s
 
 declare i32 @llvm.amdgcn.sad.u16(i32, i32, i32) #0
 
-; GCN-LABEL: {{^}}v_sad_u16:
-; GCN: v_sad_u16 v{{[0-9]+}}, v{{[0-9]+}}, s{{[0-9]+}}, s{{[0-9]+}}
 define amdgpu_kernel void @v_sad_u16(ptr addrspace(1) %out, i32 %src) {
+; SI-SDAG-LABEL: v_sad_u16:
+; SI-SDAG:       ; %bb.0:
+; SI-SDAG-NEXT:    s_load_dword s6, s[4:5], 0xb
+; SI-SDAG-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x9
+; SI-SDAG-NEXT:    s_movk_i32 s4, 0x64
+; SI-SDAG-NEXT:    s_mov_b32 s3, 0xf000
+; SI-SDAG-NEXT:    s_mov_b32 s2, -1
+; SI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-SDAG-NEXT:    v_mov_b32_e32 v0, s6
+; SI-SDAG-NEXT:    v_sad_u16 v0, v0, s4, s4
+; SI-SDAG-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; SI-SDAG-NEXT:    s_endpgm
+;
+; SI-GISEL-LABEL: v_sad_u16:
+; SI-GISEL:       ; %bb.0:
+; SI-GISEL-NEXT:    s_load_dword s3, s[4:5], 0xb
+; SI-GISEL-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x9
+; SI-GISEL-NEXT:    v_mov_b32_e32 v0, 0x64
+; SI-GISEL-NEXT:    s_mov_b32 s2, -1
+; SI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-GISEL-NEXT:    v_sad_u16 v0, s3, v0, v0
+; SI-GISEL-NEXT:    s_mov_b32 s3, 0xf000
+; SI-GISEL-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; SI-GISEL-NEXT:    s_endpgm
+;
+; VI-SDAG-LABEL: v_sad_u16:
+; VI-SDAG:       ; %bb.0:
+; VI-SDAG-NEXT:    s_load_dword s2, s[4:5], 0x2c
+; VI-SDAG-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x24
+; VI-SDAG-NEXT:    s_movk_i32 s3, 0x64
+; VI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s2
+; VI-SDAG-NEXT:    v_sad_u16 v2, v0, s3, s3
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s0
+; VI-SDAG-NEXT:    v_mov_b32_e32 v1, s1
+; VI-SDAG-NEXT:    flat_store_dword v[0:1], v2
+; VI-SDAG-NEXT:    s_endpgm
+;
+; VI-GISEL-LABEL: v_sad_u16:
+; VI-GISEL:       ; %bb.0:
+; VI-GISEL-NEXT:    s_load_dword s2, s[4:5], 0x2c
+; VI-GISEL-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x24
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, 0x64
+; VI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-GISEL-NEXT:    v_sad_u16 v2, s2, v0, v0
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, s0
+; VI-GISEL-NEXT:    v_mov_b32_e32 v1, s1
+; VI-GISEL-NEXT:    flat_store_dword v[0:1], v2
+; VI-GISEL-NEXT:    s_endpgm
   %result= call i32 @llvm.amdgcn.sad.u16(i32 %src, i32 100, i32 100) #0
   store i32 %result, ptr addrspace(1) %out, align 4
   ret void
 }
 
-; GCN-LABEL: {{^}}v_sad_u16_non_immediate:
-; GCN: v_sad_u16 v{{[0-9]+}}, s{{[0-9]+}}, v{{[0-9]+}}, v{{[0-9]+}}
 define amdgpu_kernel void @v_sad_u16_non_immediate(ptr addrspace(1) %out, i32 %src, i32 %a, i32 %b) {
+; SI-SDAG-LABEL: v_sad_u16_non_immediate:
+; SI-SDAG:       ; %bb.0:
+; SI-SDAG-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0xb
+; SI-SDAG-NEXT:    s_load_dwordx2 s[4:5], s[4:5], 0x9
+; SI-SDAG-NEXT:    s_mov_b32 s7, 0xf000
+; SI-SDAG-NEXT:    s_mov_b32 s6, -1
+; SI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-SDAG-NEXT:    v_mov_b32_e32 v0, s1
+; SI-SDAG-NEXT:    v_mov_b32_e32 v1, s2
+; SI-SDAG-NEXT:    v_sad_u16 v0, s0, v0, v1
+; SI-SDAG-NEXT:    buffer_store_dword v0, off, s[4:7], 0
+; SI-SDAG-NEXT:    s_endpgm
+;
+; SI-GISEL-LABEL: v_sad_u16_non_immediate:
+; SI-GISEL:       ; %bb.0:
+; SI-GISEL-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x9
+; SI-GISEL-NEXT:    s_load_dword s4, s[4:5], 0xd
+; SI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-GISEL-NEXT:    v_mov_b32_e32 v0, s3
+; SI-GISEL-NEXT:    v_mov_b32_e32 v1, s4
+; SI-GISEL-NEXT:    v_sad_u16 v0, s2, v0, v1
+; SI-GISEL-NEXT:    s_mov_b32 s2, -1
+; SI-GISEL-NEXT:    s_mov_b32 s3, 0xf000
+; SI-GISEL-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; SI-GISEL-NEXT:    s_endpgm
+;
+; VI-SDAG-LABEL: v_sad_u16_non_immediate:
+; VI-SDAG:       ; %bb.0:
+; VI-SDAG-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x2c
+; VI-SDAG-NEXT:    s_load_dwordx2 s[4:5], s[4:5], 0x24
+; VI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s1
+; VI-SDAG-NEXT:    v_mov_b32_e32 v1, s2
+; VI-SDAG-NEXT:    v_sad_u16 v2, s0, v0, v1
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s4
+; VI-SDAG-NEXT:    v_mov_b32_e32 v1, s5
+; VI-SDAG-NEXT:    flat_store_dword v[0:1], v2
+; VI-SDAG-NEXT:    s_endpgm
+;
+; VI-GISEL-LABEL: v_sad_u16_non_immediate:
+; VI-GISEL:       ; %bb.0:
+; VI-GISEL-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x24
+; VI-GISEL-NEXT:    s_load_dword s4, s[4:5], 0x34
+; VI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, s3
+; VI-GISEL-NEXT:    v_mov_b32_e32 v1, s4
+; VI-GISEL-NEXT:    v_sad_u16 v2, s2, v0, v1
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, s0
+; VI-GISEL-NEXT:    v_mov_b32_e32 v1, s1
+; VI-GISEL-NEXT:    flat_store_dword v[0:1], v2
+; VI-GISEL-NEXT:    s_endpgm
   %result= call i32 @llvm.amdgcn.sad.u16(i32 %src, i32 %a, i32 %b) #0
   store i32 %result, ptr addrspace(1) %out, align 4
   ret void
 }
 
 attributes #0 = { nounwind readnone }
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; SI: {{.*}}
+; VI: {{.*}}
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.u8.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.u8.ll
index d7b6f2e347471..9b006ba699199 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.u8.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.sad.u8.ll
@@ -1,22 +1,124 @@
-; RUN: llc -mtriple=amdgpu6.00 < %s | FileCheck -check-prefix=GCN %s
-; RUN: llc -mtriple=amdgpu8.03 < %s | FileCheck -check-prefix=GCN %s
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -global-isel=0 -mtriple=amdgpu6.00 < %s | FileCheck -check-prefixes=SI,SI-SDAG %s
+; RUN: llc -global-isel=1 -mtriple=amdgpu6.00 < %s | FileCheck -check-prefixes=SI,SI-GISEL %s
+; RUN: llc -global-isel=0 -mtriple=amdgpu8.03 < %s | FileCheck -check-prefixes=VI,VI-SDAG %s
+; RUN: llc -global-isel=1 -mtriple=amdgpu8.03 < %s | FileCheck -check-prefixes=VI,VI-GISEL %s
 
 declare i32 @llvm.amdgcn.sad.u8(i32, i32, i32) #0
 
-; GCN-LABEL: {{^}}v_sad_u8:
-; GCN: v_sad_u8 v{{[0-9]+}}, v{{[0-9]+}}, s{{[0-9]+}}, s{{[0-9]+}}
 define amdgpu_kernel void @v_sad_u8(ptr addrspace(1) %out, i32 %src) {
+; SI-SDAG-LABEL: v_sad_u8:
+; SI-SDAG:       ; %bb.0:
+; SI-SDAG-NEXT:    s_load_dword s6, s[4:5], 0xb
+; SI-SDAG-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x9
+; SI-SDAG-NEXT:    s_movk_i32 s4, 0x64
+; SI-SDAG-NEXT:    s_mov_b32 s3, 0xf000
+; SI-SDAG-NEXT:    s_mov_b32 s2, -1
+; SI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-SDAG-NEXT:    v_mov_b32_e32 v0, s6
+; SI-SDAG-NEXT:    v_sad_u8 v0, v0, s4, s4
+; SI-SDAG-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; SI-SDAG-NEXT:    s_endpgm
+;
+; SI-GISEL-LABEL: v_sad_u8:
+; SI-GISEL:       ; %bb.0:
+; SI-GISEL-NEXT:    s_load_dword s3, s[4:5], 0xb
+; SI-GISEL-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x9
+; SI-GISEL-NEXT:    v_mov_b32_e32 v0, 0x64
+; SI-GISEL-NEXT:    s_mov_b32 s2, -1
+; SI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-GISEL-NEXT:    v_sad_u8 v0, s3, v0, v0
+; SI-GISEL-NEXT:    s_mov_b32 s3, 0xf000
+; SI-GISEL-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; SI-GISEL-NEXT:    s_endpgm
+;
+; VI-SDAG-LABEL: v_sad_u8:
+; VI-SDAG:       ; %bb.0:
+; VI-SDAG-NEXT:    s_load_dword s2, s[4:5], 0x2c
+; VI-SDAG-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x24
+; VI-SDAG-NEXT:    s_movk_i32 s3, 0x64
+; VI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s2
+; VI-SDAG-NEXT:    v_sad_u8 v2, v0, s3, s3
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s0
+; VI-SDAG-NEXT:    v_mov_b32_e32 v1, s1
+; VI-SDAG-NEXT:    flat_store_dword v[0:1], v2
+; VI-SDAG-NEXT:    s_endpgm
+;
+; VI-GISEL-LABEL: v_sad_u8:
+; VI-GISEL:       ; %bb.0:
+; VI-GISEL-NEXT:    s_load_dword s2, s[4:5], 0x2c
+; VI-GISEL-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x24
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, 0x64
+; VI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-GISEL-NEXT:    v_sad_u8 v2, s2, v0, v0
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, s0
+; VI-GISEL-NEXT:    v_mov_b32_e32 v1, s1
+; VI-GISEL-NEXT:    flat_store_dword v[0:1], v2
+; VI-GISEL-NEXT:    s_endpgm
   %result= call i32 @llvm.amdgcn.sad.u8(i32 %src, i32 100, i32 100) #0
   store i32 %result, ptr addrspace(1) %out, align 4
   ret void
 }
 
-; GCN-LABEL: {{^}}v_sad_u8_non_immediate:
-; GCN: v_sad_u8 v{{[0-9]+}}, s{{[0-9]+}}, v{{[0-9]+}}, v{{[0-9]+}}
 define amdgpu_kernel void @v_sad_u8_non_immediate(ptr addrspace(1) %out, i32 %src, i32 %a, i32 %b) {
+; SI-SDAG-LABEL: v_sad_u8_non_immediate:
+; SI-SDAG:       ; %bb.0:
+; SI-SDAG-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0xb
+; SI-SDAG-NEXT:    s_load_dwordx2 s[4:5], s[4:5], 0x9
+; SI-SDAG-NEXT:    s_mov_b32 s7, 0xf000
+; SI-SDAG-NEXT:    s_mov_b32 s6, -1
+; SI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-SDAG-NEXT:    v_mov_b32_e32 v0, s1
+; SI-SDAG-NEXT:    v_mov_b32_e32 v1, s2
+; SI-SDAG-NEXT:    v_sad_u8 v0, s0, v0, v1
+; SI-SDAG-NEXT:    buffer_store_dword v0, off, s[4:7], 0
+; SI-SDAG-NEXT:    s_endpgm
+;
+; SI-GISEL-LABEL: v_sad_u8_non_immediate:
+; SI-GISEL:       ; %bb.0:
+; SI-GISEL-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x9
+; SI-GISEL-NEXT:    s_load_dword s4, s[4:5], 0xd
+; SI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; SI-GISEL-NEXT:    v_mov_b32_e32 v0, s3
+; SI-GISEL-NEXT:    v_mov_b32_e32 v1, s4
+; SI-GISEL-NEXT:    v_sad_u8 v0, s2, v0, v1
+; SI-GISEL-NEXT:    s_mov_b32 s2, -1
+; SI-GISEL-NEXT:    s_mov_b32 s3, 0xf000
+; SI-GISEL-NEXT:    buffer_store_dword v0, off, s[0:3], 0
+; SI-GISEL-NEXT:    s_endpgm
+;
+; VI-SDAG-LABEL: v_sad_u8_non_immediate:
+; VI-SDAG:       ; %bb.0:
+; VI-SDAG-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x2c
+; VI-SDAG-NEXT:    s_load_dwordx2 s[4:5], s[4:5], 0x24
+; VI-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s1
+; VI-SDAG-NEXT:    v_mov_b32_e32 v1, s2
+; VI-SDAG-NEXT:    v_sad_u8 v2, s0, v0, v1
+; VI-SDAG-NEXT:    v_mov_b32_e32 v0, s4
+; VI-SDAG-NEXT:    v_mov_b32_e32 v1, s5
+; VI-SDAG-NEXT:    flat_store_dword v[0:1], v2
+; VI-SDAG-NEXT:    s_endpgm
+;
+; VI-GISEL-LABEL: v_sad_u8_non_immediate:
+; VI-GISEL:       ; %bb.0:
+; VI-GISEL-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x24
+; VI-GISEL-NEXT:    s_load_dword s4, s[4:5], 0x34
+; VI-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, s3
+; VI-GISEL-NEXT:    v_mov_b32_e32 v1, s4
+; VI-GISEL-NEXT:    v_sad_u8 v2, s2, v0, v1
+; VI-GISEL-NEXT:    v_mov_b32_e32 v0, s0
+; VI-GISEL-NEXT:    v_mov_b32_e32 v1, s1
+; VI-GISEL-NEXT:    flat_store_dword v[0:1], v2
+; VI-GISEL-NEXT:    s_endpgm
   %result= call i32 @llvm.amdgcn.sad.u8(i32 %src, i32 %a, i32 %b) #0
   store i32 %result, ptr addrspace(1) %out, align 4
   ret void
 }
 
 attributes #0 = { nounwind readnone }
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; SI: {{.*}}
+; VI: {{.*}}

>From ffb986c0d9574090eeaecdb573eb94486ed1533a Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Wed, 5 Aug 2026 23:16:06 +0200
Subject: [PATCH 08/18] RuntimeLibcalls: Add sqrtf to the Hexagon runtime
 libcall set (#210909)

The library definitions go out of the way to avoid adding sqrtf, in
favor of __hexagon_sqrtf. I'm assuming that libm does provide sqrtf,
it just happens that there is a more-preferred function to use.
RuntimeLibcallsInfo should express the full set of functions that do exist,
and LibcallLoweringInfo should express the preference for which calls
should be used.

By the current ordering rules, it just so happens __hexagon_sqrtf will
win out for SQRT_F32. Add this to avoid a special case to faciliate future
libcall improvements.

Co-authored-by: Claude (Claude-Opus-4.8) <noreply at anthropic.com>
---
 llvm/include/llvm/IR/RuntimeLibcalls.td | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/include/llvm/IR/RuntimeLibcalls.td b/llvm/include/llvm/IR/RuntimeLibcalls.td
index 19b32fe029799..1c8024bfeaf91 100644
--- a/llvm/include/llvm/IR/RuntimeLibcalls.td
+++ b/llvm/include/llvm/IR/RuntimeLibcalls.td
@@ -2766,7 +2766,7 @@ def HexagonSystemLibrary
     : SystemRuntimeLibrary<isHexagon,
     (add (sub DefaultLibcallImpls32,
     __adddf3, __divsf3, __udivsi3, __udivdi3,
-    __umoddi3, __divdf3, __muldf3, __divsi3, __subdf3, sqrtf,
+    __umoddi3, __divdf3, __muldf3, __divsi3, __subdf3,
     __divdi3, __umodsi3, __moddi3, __modsi3), HexagonLibcalls,
     LibmHasSinCosF32, LibmHasSinCosF64, LibmHasSinCosF128,
     exp10f, exp10, exp10l_f128, __stack_chk_fail, __stack_chk_guard,

>From 8b5fb5394b690b07f1d0a2b09b6af7984f473fb7 Mon Sep 17 00:00:00 2001
From: rlougher <56726327+rlougher at users.noreply.github.com>
Date: Wed, 5 Aug 2026 22:22:34 +0100
Subject: [PATCH 09/18] [CodeGen] ReplaceWithVeclib assertion failure with
 invalid intrinsic (#211352)

Commit f6a359f (#194639) removed an intrinsic ID check against
Intrinsic::not_intrinsic.

This check is needed because a call instruction can be cast to an
intrinsic instruction if the called function's name starts with "llvm."
(see llvm::Function::isIntrinsic).

This means if ReplaceWithVeclib is given an invalid intrinsic, it will
fail with an assertion failure.
---
 llvm/lib/CodeGen/ReplaceWithVeclib.cpp             |  2 +-
 .../ReplaceWithVeclib/veclib-invalid-intrinsic.ll  | 14 ++++++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/Transforms/ReplaceWithVeclib/veclib-invalid-intrinsic.ll

diff --git a/llvm/lib/CodeGen/ReplaceWithVeclib.cpp b/llvm/lib/CodeGen/ReplaceWithVeclib.cpp
index 4a240c914a252..c2a7835504e67 100644
--- a/llvm/lib/CodeGen/ReplaceWithVeclib.cpp
+++ b/llvm/lib/CodeGen/ReplaceWithVeclib.cpp
@@ -300,7 +300,7 @@ static bool runImpl(const TargetLibraryInfo &TLI, Function &F) {
   SmallVector<Instruction *> ReplacedCalls;
   for (auto &I : instructions(F)) {
     auto *II = dyn_cast<IntrinsicInst>(&I);
-    if (!II)
+    if (!II || II->getIntrinsicID() == Intrinsic::not_intrinsic)
       continue;
 
     // Vector llvm.sincos returns a struct so it does not fit the generic
diff --git a/llvm/test/Transforms/ReplaceWithVeclib/veclib-invalid-intrinsic.ll b/llvm/test/Transforms/ReplaceWithVeclib/veclib-invalid-intrinsic.ll
new file mode 100644
index 0000000000000..2439110e1a7d3
--- /dev/null
+++ b/llvm/test/Transforms/ReplaceWithVeclib/veclib-invalid-intrinsic.ll
@@ -0,0 +1,14 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=replace-with-veclib -S < %s | FileCheck %s
+
+declare <4 x float> @llvm.invalid.intrinsic(<4 x float>)
+
+define <4 x float> @test(<4 x float> %v) {
+; CHECK-LABEL: define <4 x float> @test(
+; CHECK-SAME: <4 x float> [[V:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call <4 x float> @llvm.invalid.intrinsic(<4 x float> [[V]])
+; CHECK-NEXT:    ret <4 x float> [[R]]
+;
+  %r = call <4 x float> @llvm.invalid.intrinsic(<4 x float> %v)
+  ret <4 x float> %r
+}

>From 808694fe6e20c25d3da2c6ecf253cea4df9f83d1 Mon Sep 17 00:00:00 2001
From: David Green <david.green at arm.com>
Date: Wed, 5 Aug 2026 22:24:53 +0100
Subject: [PATCH 10/18] [AArch64][GlobalISel] Cleanup old selection code for
 G_OR and fp instructions. (#213869)

G_OR can be selected via tablegen patterns, and other fp elements of
selectBinOp were no longer used. unsupportedBinOp was just testing
things that should always be true.
---
 .../GISel/AArch64InstructionSelector.cpp      | 100 +-----------------
 1 file changed, 4 insertions(+), 96 deletions(-)

diff --git a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
index 7e9135b15144a..eabacd1278b69 100644
--- a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
+++ b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
@@ -778,62 +778,12 @@ static std::optional<uint64_t> getImmedFromMO(const MachineOperand &Root) {
   return Immed;
 }
 
-/// Check whether \p I is a currently unsupported binary operation:
-/// - it has an unsized type
-/// - an operand is not a vreg
-/// - all operands are not in the same bank
-/// These are checks that should someday live in the verifier, but right now,
-/// these are mostly limitations of the aarch64 selector.
-static bool unsupportedBinOp(const MachineInstr &I,
-                             const AArch64RegisterBankInfo &RBI,
-                             const MachineRegisterInfo &MRI,
-                             const AArch64RegisterInfo &TRI) {
-  LLT Ty = MRI.getType(I.getOperand(0).getReg());
-  if (!Ty.isValid()) {
-    LLVM_DEBUG(dbgs() << "Generic binop register should be typed\n");
-    return true;
-  }
-
-  const RegisterBank *PrevOpBank = nullptr;
-  for (auto &MO : I.operands()) {
-    // FIXME: Support non-register operands.
-    if (!MO.isReg()) {
-      LLVM_DEBUG(dbgs() << "Generic inst non-reg operands are unsupported\n");
-      return true;
-    }
-
-    // FIXME: Can generic operations have physical registers operands? If
-    // so, this will need to be taught about that, and we'll need to get the
-    // bank out of the minimal class for the register.
-    // Either way, this needs to be documented (and possibly verified).
-    if (!MO.getReg().isVirtual()) {
-      LLVM_DEBUG(dbgs() << "Generic inst has physical register operand\n");
-      return true;
-    }
-
-    const RegisterBank *OpBank = RBI.getRegBank(MO.getReg(), MRI, TRI);
-    if (!OpBank) {
-      LLVM_DEBUG(dbgs() << "Generic register has no bank or class\n");
-      return true;
-    }
-
-    if (PrevOpBank && OpBank != PrevOpBank) {
-      LLVM_DEBUG(dbgs() << "Generic inst operands have different banks\n");
-      return true;
-    }
-    PrevOpBank = OpBank;
-  }
-  return false;
-}
-
-/// Select the AArch64 opcode for the basic binary operation \p GenericOpc
-/// (such as G_OR or G_SDIV), appropriate for the register bank \p RegBankID
-/// and of size \p OpSize.
+/// Select the AArch64 opcode for the basic binary operation \p GenericOpc,
+/// appropriate for the register bank \p RegBankID and of size \p OpSize.
 /// \returns \p GenericOpc if the combination is unsupported.
 static unsigned selectBinaryOp(unsigned GenericOpc, unsigned RegBankID,
                                unsigned OpSize) {
-  switch (RegBankID) {
-  case AArch64::GPRRegBankID:
+  if (RegBankID == AArch64::GPRRegBankID) {
     if (OpSize == 32) {
       switch (GenericOpc) {
       case TargetOpcode::G_SHL:
@@ -847,8 +797,6 @@ static unsigned selectBinaryOp(unsigned GenericOpc, unsigned RegBankID,
       }
     } else if (OpSize == 64) {
       switch (GenericOpc) {
-      case TargetOpcode::G_PTR_ADD:
-        return AArch64::ADDXrr;
       case TargetOpcode::G_SHL:
         return AArch64::LSLVXr;
       case TargetOpcode::G_LSHR:
@@ -859,39 +807,6 @@ static unsigned selectBinaryOp(unsigned GenericOpc, unsigned RegBankID,
         return GenericOpc;
       }
     }
-    break;
-  case AArch64::FPRRegBankID:
-    switch (OpSize) {
-    case 32:
-      switch (GenericOpc) {
-      case TargetOpcode::G_FADD:
-        return AArch64::FADDSrr;
-      case TargetOpcode::G_FSUB:
-        return AArch64::FSUBSrr;
-      case TargetOpcode::G_FMUL:
-        return AArch64::FMULSrr;
-      case TargetOpcode::G_FDIV:
-        return AArch64::FDIVSrr;
-      default:
-        return GenericOpc;
-      }
-    case 64:
-      switch (GenericOpc) {
-      case TargetOpcode::G_FADD:
-        return AArch64::FADDDrr;
-      case TargetOpcode::G_FSUB:
-        return AArch64::FSUBDrr;
-      case TargetOpcode::G_FMUL:
-        return AArch64::FMULDrr;
-      case TargetOpcode::G_FDIV:
-        return AArch64::FDIVDrr;
-      case TargetOpcode::G_OR:
-        return AArch64::ORRv8i8;
-      default:
-        return GenericOpc;
-      }
-    }
-    break;
   }
   return GenericOpc;
 }
@@ -3161,7 +3076,7 @@ bool AArch64InstructionSelector::select(MachineInstr &I) {
     if (MRI.getType(I.getOperand(0).getReg()).isVector())
       return selectVectorAshrLshr(I, MRI);
     [[fallthrough]];
-  case TargetOpcode::G_SHL:
+  case TargetOpcode::G_SHL: {
     if (Opcode == TargetOpcode::G_SHL &&
         MRI.getType(I.getOperand(0).getReg()).isVector())
       return selectVectorSHL(I, MRI);
@@ -3185,14 +3100,8 @@ bool AArch64InstructionSelector::select(MachineInstr &I) {
         I.getOperand(2).setReg(Trunc.getReg(0));
       }
     }
-    [[fallthrough]];
-  case TargetOpcode::G_OR: {
-    // Reject the various things we don't support yet.
-    if (unsupportedBinOp(I, RBI, MRI, TRI))
-      return false;
 
     const unsigned OpSize = Ty.getSizeInBits();
-
     const Register DefReg = I.getOperand(0).getReg();
     const RegisterBank &RB = *RBI.getRegBank(DefReg, MRI, TRI);
 
@@ -3208,7 +3117,6 @@ bool AArch64InstructionSelector::select(MachineInstr &I) {
     constrainSelectedInstRegOperands(I, TII, TRI, RBI);
     return true;
   }
-
   case TargetOpcode::G_PTR_ADD: {
     emitADD(I.getOperand(0).getReg(), I.getOperand(1), I.getOperand(2), MIB);
     I.eraseFromParent();

>From a713525a5120b7d67e3f69baaf4e3cbbdd1e69c4 Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Wed, 5 Aug 2026 23:48:05 +0200
Subject: [PATCH 11/18] RuntimeLibcalls: Add generic FCMP3_F* three-way compare
 for single-symbol ABIs (#211618)

MSP430's __mspabi_cmpd/__mspabi_cmpf are one three-way compare symbol
serving every predicate, previously modeled as six suffixed impls each. Replace
them with a single generic operator FCMP3_*, and give softenSetCCOperands a
3rd lowering option. After the boolean O*_F* and the per-predicate
FCMP3_<pred>_F* helpers, use the generic FCMP3_F* helper tested with the predicate's
condition code.

Also opt __nedf2 out of the MSP430 default set: it was the only libgcc
F64 compare not already opted out, so it would otherwise provide
FCMP3_UNE_F64 and win over __mspabi_cmpd for not-equal.

Co-authored-by: Claude (Opus 4.8) <noreply at anthropic.com>
---
 llvm/include/llvm/IR/RuntimeLibcalls.td       | 35 +++-----
 .../CodeGen/SelectionDAG/TargetLowering.cpp   | 85 ++++++++++---------
 llvm/lib/Target/MSP430/MSP430Subtarget.cpp    | 28 +++---
 3 files changed, 69 insertions(+), 79 deletions(-)

diff --git a/llvm/include/llvm/IR/RuntimeLibcalls.td b/llvm/include/llvm/IR/RuntimeLibcalls.td
index 1c8024bfeaf91..b0d9663cca395 100644
--- a/llvm/include/llvm/IR/RuntimeLibcalls.td
+++ b/llvm/include/llvm/IR/RuntimeLibcalls.td
@@ -483,7 +483,9 @@ def CONVERT_PPCF128_F128 : RuntimeLibcall;
 //
 // The O*_F* and UO_F* libcalls return a simple 0/1 boolean value.
 //
-// The FCMP3_PRED_*_F* libcalls return a three-way (-1/0/1) result.
+// The FCMP3_PRED_*_F* libcalls return a three-way (-1/0/1) result, one symbol
+// per predicate. FCMP3_F* is a generic three-way compare for ABIs with one
+// symbol serving every predicate (e.g. MSP430's __mspabi_cmpd).
 foreach FPTy = ["F32", "F64", "F128", "PPCF128"] in {
   def OEQ_#FPTy : RuntimeLibcall;
   def UNE_#FPTy : RuntimeLibcall;
@@ -499,6 +501,8 @@ foreach FPTy = ["F32", "F64", "F128", "PPCF128"] in {
   def FCMP3_PRED_OLT_#FPTy : RuntimeLibcall;
   def FCMP3_PRED_OLE_#FPTy : RuntimeLibcall;
   def FCMP3_PRED_OGT_#FPTy : RuntimeLibcall;
+
+  def FCMP3_#FPTy : RuntimeLibcall;
 }
 
 // Memory
@@ -2884,18 +2888,9 @@ def __mspabi_fltulf : RuntimeLibcallImpl<UINTTOFP_I32_F32>;
 def __mspabi_fltullf : RuntimeLibcallImpl<UINTTOFP_I64_F32>;
 
 // Floating point comparisons - EABI Table 7
-def __mspabi_cmpd__oeq : RuntimeLibcallImpl<FCMP3_PRED_OEQ_F64, "__mspabi_cmpd">;
-def __mspabi_cmpd__une : RuntimeLibcallImpl<FCMP3_PRED_UNE_F64, "__mspabi_cmpd">;
-def __mspabi_cmpd__oge : RuntimeLibcallImpl<FCMP3_PRED_OGE_F64, "__mspabi_cmpd">;
-def __mspabi_cmpd__olt : RuntimeLibcallImpl<FCMP3_PRED_OLT_F64, "__mspabi_cmpd">;
-def __mspabi_cmpd__ole : RuntimeLibcallImpl<FCMP3_PRED_OLE_F64, "__mspabi_cmpd">;
-def __mspabi_cmpd__ogt : RuntimeLibcallImpl<FCMP3_PRED_OGT_F64, "__mspabi_cmpd">;
-def __mspabi_cmpf__oeq : RuntimeLibcallImpl<FCMP3_PRED_OEQ_F32, "__mspabi_cmpf">;
-def __mspabi_cmpf__une : RuntimeLibcallImpl<FCMP3_PRED_UNE_F32, "__mspabi_cmpf">;
-def __mspabi_cmpf__oge : RuntimeLibcallImpl<FCMP3_PRED_OGE_F32, "__mspabi_cmpf">;
-def __mspabi_cmpf__olt : RuntimeLibcallImpl<FCMP3_PRED_OLT_F32, "__mspabi_cmpf">;
-def __mspabi_cmpf__ole : RuntimeLibcallImpl<FCMP3_PRED_OLE_F32, "__mspabi_cmpf">;
-def __mspabi_cmpf__ogt : RuntimeLibcallImpl<FCMP3_PRED_OGT_F32, "__mspabi_cmpf">;
+// A single three-way compare symbol serves every predicate.
+def __mspabi_cmpd : RuntimeLibcallImpl<FCMP3_F64>;
+def __mspabi_cmpf : RuntimeLibcallImpl<FCMP3_F32>;
 
 // Floating point arithmetic - EABI Table 8
 def __mspabi_addd : RuntimeLibcallImpl<ADD_F64>;
@@ -3022,18 +3017,8 @@ def MSP430SystemLibrary
       __mspabi_fltullf,
 
       // Floating point comparisons - EABI Table 7
-      LibcallsWithCC<(add __mspabi_cmpd__oeq,
-                          __mspabi_cmpd__une,
-                          __mspabi_cmpd__oge,
-                          __mspabi_cmpd__olt,
-                          __mspabi_cmpd__ole,
-                          __mspabi_cmpd__ogt), MSP430_BUILTIN>,
-      __mspabi_cmpf__oeq,
-      __mspabi_cmpf__une,
-      __mspabi_cmpf__oge,
-      __mspabi_cmpf__olt,
-      __mspabi_cmpf__ole,
-      __mspabi_cmpf__ogt,
+      LibcallsWithCC<(add __mspabi_cmpd), MSP430_BUILTIN>,
+      __mspabi_cmpf,
 
       // Floating point arithmetic - EABI Table 8
       LibcallsWithCC<(add __mspabi_addd,
diff --git a/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
index 86c0a2adb35b9..46e11ede878a1 100644
--- a/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
@@ -324,14 +324,19 @@ void TargetLowering::softenSetCCOperands(SelectionDAG &DAG, EVT VT,
 
 /// Select the libcall and the condition code to test its result against 0 for
 /// an ordered floating-point compare. \p BoolLC is the boolean helper (result
-/// is 0/1); \p TriStateLC is the three-way helper (result is -1/0/1, tested
-/// against 0 with \p TriStateCC). The boolean form is preferred when available.
+/// is 0/1). \p TriStateLC is the per-predicate three-way helper and \p
+/// GenericLC the generic single-symbol three-way helper (both return -1/0/1,
+/// tested against 0 with \p TriStateCC). The boolean form is preferred, then
+/// the per-predicate three-way, then the generic three-way.
 static std::pair<RTLIB::Libcall, ISD::CondCode>
 selectFPCmpLibcall(const LibcallLoweringInfo &Libcalls, RTLIB::Libcall BoolLC,
-                   RTLIB::Libcall TriStateLC, ISD::CondCode TriStateCC) {
+                   RTLIB::Libcall TriStateLC, RTLIB::Libcall GenericLC,
+                   ISD::CondCode TriStateCC) {
   if (Libcalls.getLibcallImpl(BoolLC) != RTLIB::Unsupported)
     return {BoolLC, ISD::SETNE};
-  return {TriStateLC, TriStateCC};
+  if (Libcalls.getLibcallImpl(TriStateLC) != RTLIB::Unsupported)
+    return {TriStateLC, TriStateCC};
+  return {GenericLC, TriStateCC};
 }
 
 void TargetLowering::softenSetCCOperands(SelectionDAG &DAG, EVT VT,
@@ -363,47 +368,47 @@ void TargetLowering::softenSetCCOperands(SelectionDAG &DAG, EVT VT,
   switch (CCCode) {
   case ISD::SETEQ:
   case ISD::SETOEQ:
-    std::tie(LC1, CC1) =
-        selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(OEQ),
-                           FP_CMP_LIBCALL(FCMP3_PRED_OEQ), ISD::SETEQ);
+    std::tie(LC1, CC1) = selectFPCmpLibcall(
+        DAG.getLibcalls(), FP_CMP_LIBCALL(OEQ), FP_CMP_LIBCALL(FCMP3_PRED_OEQ),
+        FP_CMP_LIBCALL(FCMP3), ISD::SETEQ);
     break;
   case ISD::SETNE:
   case ISD::SETUNE:
-    std::tie(LC1, CC1) =
-        selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(UNE),
-                           FP_CMP_LIBCALL(FCMP3_PRED_UNE), ISD::SETNE);
+    std::tie(LC1, CC1) = selectFPCmpLibcall(
+        DAG.getLibcalls(), FP_CMP_LIBCALL(UNE), FP_CMP_LIBCALL(FCMP3_PRED_UNE),
+        FP_CMP_LIBCALL(FCMP3), ISD::SETNE);
     // Some ABIs (e.g. AEABI) provide neither a not-equal nor a three-way
     // compare; obtain not-equal (UNE = !OEQ) by inverting ordered-equal.
     if (DAG.getLibcalls().getLibcallImpl(LC1) == RTLIB::Unsupported) {
-      std::tie(LC1, CC1) =
-          selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(OEQ),
-                             FP_CMP_LIBCALL(FCMP3_PRED_OEQ), ISD::SETEQ);
+      std::tie(LC1, CC1) = selectFPCmpLibcall(
+          DAG.getLibcalls(), FP_CMP_LIBCALL(OEQ),
+          FP_CMP_LIBCALL(FCMP3_PRED_OEQ), FP_CMP_LIBCALL(FCMP3), ISD::SETEQ);
       ShouldInvertCC = true;
     }
     break;
   case ISD::SETGE:
   case ISD::SETOGE:
-    std::tie(LC1, CC1) =
-        selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(OGE),
-                           FP_CMP_LIBCALL(FCMP3_PRED_OGE), ISD::SETGE);
+    std::tie(LC1, CC1) = selectFPCmpLibcall(
+        DAG.getLibcalls(), FP_CMP_LIBCALL(OGE), FP_CMP_LIBCALL(FCMP3_PRED_OGE),
+        FP_CMP_LIBCALL(FCMP3), ISD::SETGE);
     break;
   case ISD::SETLT:
   case ISD::SETOLT:
-    std::tie(LC1, CC1) =
-        selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(OLT),
-                           FP_CMP_LIBCALL(FCMP3_PRED_OLT), ISD::SETLT);
+    std::tie(LC1, CC1) = selectFPCmpLibcall(
+        DAG.getLibcalls(), FP_CMP_LIBCALL(OLT), FP_CMP_LIBCALL(FCMP3_PRED_OLT),
+        FP_CMP_LIBCALL(FCMP3), ISD::SETLT);
     break;
   case ISD::SETLE:
   case ISD::SETOLE:
-    std::tie(LC1, CC1) =
-        selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(OLE),
-                           FP_CMP_LIBCALL(FCMP3_PRED_OLE), ISD::SETLE);
+    std::tie(LC1, CC1) = selectFPCmpLibcall(
+        DAG.getLibcalls(), FP_CMP_LIBCALL(OLE), FP_CMP_LIBCALL(FCMP3_PRED_OLE),
+        FP_CMP_LIBCALL(FCMP3), ISD::SETLE);
     break;
   case ISD::SETGT:
   case ISD::SETOGT:
-    std::tie(LC1, CC1) =
-        selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(OGT),
-                           FP_CMP_LIBCALL(FCMP3_PRED_OGT), ISD::SETGT);
+    std::tie(LC1, CC1) = selectFPCmpLibcall(
+        DAG.getLibcalls(), FP_CMP_LIBCALL(OGT), FP_CMP_LIBCALL(FCMP3_PRED_OGT),
+        FP_CMP_LIBCALL(FCMP3), ISD::SETGT);
     break;
   case ISD::SETO:
     ShouldInvertCC = true;
@@ -420,33 +425,33 @@ void TargetLowering::softenSetCCOperands(SelectionDAG &DAG, EVT VT,
   case ISD::SETUEQ:
     LC1 = FP_CMP_LIBCALL(UO);
     CC1 = ISD::SETNE;
-    std::tie(LC2, CC2) =
-        selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(OEQ),
-                           FP_CMP_LIBCALL(FCMP3_PRED_OEQ), ISD::SETEQ);
+    std::tie(LC2, CC2) = selectFPCmpLibcall(
+        DAG.getLibcalls(), FP_CMP_LIBCALL(OEQ), FP_CMP_LIBCALL(FCMP3_PRED_OEQ),
+        FP_CMP_LIBCALL(FCMP3), ISD::SETEQ);
     break;
   default:
     // Invert CC for unordered comparisons, handled by the ordered inverse.
     ShouldInvertCC = true;
     switch (CCCode) {
     case ISD::SETULT:
-      std::tie(LC1, CC1) =
-          selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(OGE),
-                             FP_CMP_LIBCALL(FCMP3_PRED_OGE), ISD::SETGE);
+      std::tie(LC1, CC1) = selectFPCmpLibcall(
+          DAG.getLibcalls(), FP_CMP_LIBCALL(OGE),
+          FP_CMP_LIBCALL(FCMP3_PRED_OGE), FP_CMP_LIBCALL(FCMP3), ISD::SETGE);
       break;
     case ISD::SETULE:
-      std::tie(LC1, CC1) =
-          selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(OGT),
-                             FP_CMP_LIBCALL(FCMP3_PRED_OGT), ISD::SETGT);
+      std::tie(LC1, CC1) = selectFPCmpLibcall(
+          DAG.getLibcalls(), FP_CMP_LIBCALL(OGT),
+          FP_CMP_LIBCALL(FCMP3_PRED_OGT), FP_CMP_LIBCALL(FCMP3), ISD::SETGT);
       break;
     case ISD::SETUGT:
-      std::tie(LC1, CC1) =
-          selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(OLE),
-                             FP_CMP_LIBCALL(FCMP3_PRED_OLE), ISD::SETLE);
+      std::tie(LC1, CC1) = selectFPCmpLibcall(
+          DAG.getLibcalls(), FP_CMP_LIBCALL(OLE),
+          FP_CMP_LIBCALL(FCMP3_PRED_OLE), FP_CMP_LIBCALL(FCMP3), ISD::SETLE);
       break;
     case ISD::SETUGE:
-      std::tie(LC1, CC1) =
-          selectFPCmpLibcall(DAG.getLibcalls(), FP_CMP_LIBCALL(OLT),
-                             FP_CMP_LIBCALL(FCMP3_PRED_OLT), ISD::SETLT);
+      std::tie(LC1, CC1) = selectFPCmpLibcall(
+          DAG.getLibcalls(), FP_CMP_LIBCALL(OLT),
+          FP_CMP_LIBCALL(FCMP3_PRED_OLT), FP_CMP_LIBCALL(FCMP3), ISD::SETLT);
       break;
     default:
       llvm_unreachable("Do not know how to soften this setcc!");
diff --git a/llvm/lib/Target/MSP430/MSP430Subtarget.cpp b/llvm/lib/Target/MSP430/MSP430Subtarget.cpp
index dbe0489be356b..6d4d6e2298156 100644
--- a/llvm/lib/Target/MSP430/MSP430Subtarget.cpp
+++ b/llvm/lib/Target/MSP430/MSP430Subtarget.cpp
@@ -158,20 +158,20 @@ void MSP430Subtarget::initLibcallLoweringInfo(LibcallLoweringInfo &Info) const {
       {RTLIB::SINTTOFP_I64_F32, RTLIB::impl___mspabi_fltllif},
       {RTLIB::UINTTOFP_I32_F32, RTLIB::impl___mspabi_fltulf},
       {RTLIB::UINTTOFP_I64_F32, RTLIB::impl___mspabi_fltullf},
-      // Floating point comparisons - EABI Table 7. These are three-way
-      // compares returning -1/0/1, so they implement the FCMP3_PRED_* libcalls.
-      {RTLIB::FCMP3_PRED_OEQ_F64, RTLIB::impl___mspabi_cmpd__oeq},
-      {RTLIB::FCMP3_PRED_UNE_F64, RTLIB::impl___mspabi_cmpd__une},
-      {RTLIB::FCMP3_PRED_OGE_F64, RTLIB::impl___mspabi_cmpd__oge},
-      {RTLIB::FCMP3_PRED_OLT_F64, RTLIB::impl___mspabi_cmpd__olt},
-      {RTLIB::FCMP3_PRED_OLE_F64, RTLIB::impl___mspabi_cmpd__ole},
-      {RTLIB::FCMP3_PRED_OGT_F64, RTLIB::impl___mspabi_cmpd__ogt},
-      {RTLIB::FCMP3_PRED_OEQ_F32, RTLIB::impl___mspabi_cmpf__oeq},
-      {RTLIB::FCMP3_PRED_UNE_F32, RTLIB::impl___mspabi_cmpf__une},
-      {RTLIB::FCMP3_PRED_OGE_F32, RTLIB::impl___mspabi_cmpf__oge},
-      {RTLIB::FCMP3_PRED_OLT_F32, RTLIB::impl___mspabi_cmpf__olt},
-      {RTLIB::FCMP3_PRED_OLE_F32, RTLIB::impl___mspabi_cmpf__ole},
-      {RTLIB::FCMP3_PRED_OGT_F32, RTLIB::impl___mspabi_cmpf__ogt},
+      // Floating point comparisons - EABI Table 7. A single three-way compare
+      // symbol serves every predicate.
+      {RTLIB::FCMP3_PRED_OEQ_F64, RTLIB::impl___mspabi_cmpd},
+      {RTLIB::FCMP3_PRED_UNE_F64, RTLIB::impl___mspabi_cmpd},
+      {RTLIB::FCMP3_PRED_OGE_F64, RTLIB::impl___mspabi_cmpd},
+      {RTLIB::FCMP3_PRED_OLT_F64, RTLIB::impl___mspabi_cmpd},
+      {RTLIB::FCMP3_PRED_OLE_F64, RTLIB::impl___mspabi_cmpd},
+      {RTLIB::FCMP3_PRED_OGT_F64, RTLIB::impl___mspabi_cmpd},
+      {RTLIB::FCMP3_PRED_OEQ_F32, RTLIB::impl___mspabi_cmpf},
+      {RTLIB::FCMP3_PRED_UNE_F32, RTLIB::impl___mspabi_cmpf},
+      {RTLIB::FCMP3_PRED_OGE_F32, RTLIB::impl___mspabi_cmpf},
+      {RTLIB::FCMP3_PRED_OLT_F32, RTLIB::impl___mspabi_cmpf},
+      {RTLIB::FCMP3_PRED_OLE_F32, RTLIB::impl___mspabi_cmpf},
+      {RTLIB::FCMP3_PRED_OGT_F32, RTLIB::impl___mspabi_cmpf},
       // Floating point arithmetic - EABI Table 8.
       {RTLIB::ADD_F64, RTLIB::impl___mspabi_addd},
       {RTLIB::SUB_F64, RTLIB::impl___mspabi_subd},

>From 1ebbbc842b9755992e003660165df8f2f2ed9f05 Mon Sep 17 00:00:00 2001
From: David Green <david.green at arm.com>
Date: Wed, 5 Aug 2026 22:52:31 +0100
Subject: [PATCH 12/18] [AArch64] Move tryFoldCselToFMaxMin to Select. NFC
 (#214346)

This moves the code out of PreprocessISelDAG into Select where it should
be performed.
---
 .../Target/AArch64/AArch64ISelDAGToDAG.cpp    | 50 +++++++++----------
 1 file changed, 25 insertions(+), 25 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
index f6b90594b064e..95f57346f0c5e 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
@@ -452,7 +452,7 @@ class AArch64DAGToDAGISel : public SelectionDAGISel {
 
   bool trySelectXAR(SDNode *N);
 
-  SDValue tryFoldCselToFMaxMin(SDNode &N);
+  bool tryFoldCselToFMaxMin(SDNode *N);
 
 // Include the pieces autogenerated from the target description.
 #include "AArch64GenDAGISel.inc"
@@ -5229,6 +5229,11 @@ void AArch64DAGToDAGISel::Select(SDNode *Node) {
     break;
   }
 
+  case AArch64ISD::CSEL:
+    if (tryFoldCselToFMaxMin(Node))
+      return;
+    break;
+
   case ISD::Constant: {
     // Materialize zero constants as copies from WZR/XZR.  This allows
     // the coalescer to propagate these into other instructions.
@@ -8203,24 +8208,24 @@ bool AArch64DAGToDAGISel::SelectCmpBranchExtOperand(SDValue N, SDValue &Reg,
 /// The nsz requirement is needed only when C is zero, to avoid signed-zero
 /// mismatches. The never-sNaN check is required because AArch64 FMAXNM/FMINNM
 /// differ from fcmp+fcsel for signaling NaN inputs.
-SDValue AArch64DAGToDAGISel::tryFoldCselToFMaxMin(SDNode &N) {
-  EVT VT = N.getValueType(0);
+bool AArch64DAGToDAGISel::tryFoldCselToFMaxMin(SDNode *N) {
+  EVT VT = N->getValueType(0);
 
   // Scalar FP only.
   if (!VT.isFloatingPoint() || VT.isVector())
-    return SDValue();
+    return false;
 
-  SDValue TVal = N.getOperand(0);
-  SDValue FVal = N.getOperand(1);
-  SDValue CCVal = N.getOperand(2);
-  SDValue Cmp = N.getOperand(3);
+  SDValue TVal = N->getOperand(0);
+  SDValue FVal = N->getOperand(1);
+  SDValue CCVal = N->getOperand(2);
+  SDValue Cmp = N->getOperand(3);
 
   if (Cmp.getOpcode() != AArch64ISD::FCMP)
-    return SDValue();
+    return false;
 
   auto *CC = dyn_cast<ConstantSDNode>(CCVal);
   if (!CC)
-    return SDValue();
+    return false;
 
   SDValue CmpLHS = Cmp.getOperand(0);
   SDValue CmpRHS = Cmp.getOperand(1);
@@ -8244,41 +8249,39 @@ SDValue AArch64DAGToDAGISel::tryFoldCselToFMaxMin(SDNode &N) {
     if (TVal == CmpLHS && FVal == CmpRHS)
       isMax = true;
     else
-      return SDValue();
+      return false;
   } else if (CondCode == AArch64CC::MI || CondCode == AArch64CC::LS) {
     if (TVal == CmpLHS && FVal == CmpRHS)
       isMax = false;
     else
-      return SDValue();
+      return false;
   } else {
-    return SDValue();
+    return false;
   }
 
   // Get the machine opcode for this VT and operation.
   unsigned Opc = getOpc(VT, isMax);
   if (!Opc)
-    return SDValue();
+    return false;
 
   // Constant must be non-NaN.
   auto *CFP = dyn_cast<ConstantFPSDNode>(CmpRHS);
   if (!CFP || CFP->getValueAPF().isNaN())
-    return SDValue();
+    return false;
 
   // nsz flag required only when constant is zero: fmaxnm(+0,-0)=+0 differs from
   // fcmp+select's -0. For non-zero constants, semantics are identical.
-  if (CFP->isZero() && !N.getFlags().hasNoSignedZeros())
-    return SDValue();
+  if (CFP->isZero() && !N->getFlags().hasNoSignedZeros())
+    return false;
 
   // Only fold if variable operand is never sNaN.
   // This runs after DAG combines, so later combines cannot remove a defining
   // operation used by isKnownNeverSNaN().
   if (!CurDAG->isKnownNeverSNaN(CmpLHS))
-    return SDValue();
-
-  SDLoc DL(&N);
+    return false;
 
-  // Directly emit the machine node
-  return SDValue(CurDAG->getMachineNode(Opc, DL, VT, CmpLHS, CmpRHS), 0);
+  CurDAG->SelectNodeTo(N, Opc, VT, CmpLHS, CmpRHS);
+  return true;
 }
 
 void AArch64DAGToDAGISel::PreprocessISelDAG() {
@@ -8297,9 +8300,6 @@ void AArch64DAGToDAGISel::PreprocessISelDAG() {
 
       break;
     }
-    case AArch64ISD::CSEL:
-      Result = tryFoldCselToFMaxMin(N);
-      break;
     default:
       break;
     }

>From 5917e33715c63f2af5dc853b053b8a8d1964d3e6 Mon Sep 17 00:00:00 2001
From: Chen Li <thechenli.dev at gmail.com>
Date: Wed, 5 Aug 2026 14:53:31 -0700
Subject: [PATCH 13/18] [lldb][elf-core] Populate memory region names from
 NT_FILE (#212666)

## Summary

`ProcessElfCore` already parses `NT_FILE`, but its cached `PT_LOAD`
memory-region entries did not retain their backing filenames.

- cache a complete `MemoryRegionInfo` for each `PT_LOAD` instead of
rebuilding one from a custom permissions/name record on every query
- associate an `NT_FILE` pathname when the `PT_LOAD` and `NT_FILE`
starts match; their ends may differ, while a `PT_LOAD` beginning inside
an `NT_FILE` range remains unnamed
- finalize names and memory-tag state after all program headers are
parsed, making the result independent of `PT_LOAD` and `PT_NOTE`
ordering
- preserve regions with `p_filesz == 0` and return the cached region
directly from `DoGetMemoryRegionInfo`
- add API coverage for the same-start/different-end case, an interior
unnamed region, and an unnamed NT_FILE-only tail

## Testing

- Clean LLVM 24/LLDB build completed successfully.
- `check-lldb-api-functionalities-postmortem-elf-core` (4/4 passed)
- `ProcessElfCoreTests` (3/3 passed)
- `check-lldb-api-linux-aarch64-mte_core_file` (1/1 passed)
- Real IPNext core compatibility smoke test using an assertion-disabled
Release build:

```
(lldb) memory region 0x7fc4f25fd000
[0x00007fc4f25fd000-0x00007fc4f2600000) r-- /tmp/aot_inductor_loaded_modelaCZzUI/ckq4hskzrtwkbxruge7ofytm2zmahqgcizkszojgcp3eepdubxc2.hsaco
```

The focused API fixture exercises the mismatched-end and interior-start
policies; the production core verifies compatibility with a large
zero-file-size HSACO mapping.

Co-authored-by: Chen Li <chenlii at fb.com>
---
 .../Process/elf-core/ProcessElfCore.cpp       | 130 ++++++++++++------
 .../Plugins/Process/elf-core/ProcessElfCore.h |  12 +-
 .../postmortem/elf-core/TestLinuxCore.py      |  69 ++++++++++
 .../elf-core/elf-NT_FILE-memory-region.yaml   |  30 ++++
 4 files changed, 196 insertions(+), 45 deletions(-)
 create mode 100644 lldb/test/API/functionalities/postmortem/elf-core/elf-NT_FILE-memory-region.yaml

diff --git a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp
index 4cc760de54a5c..1b3617c90e3e7 100644
--- a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp
+++ b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp
@@ -6,9 +6,11 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <algorithm>
 #include <cstdlib>
 
 #include <memory>
+#include <vector>
 
 #include "lldb/Core/Module.h"
 #include "lldb/Core/ModuleSpec.h"
@@ -132,15 +134,19 @@ lldb::addr_t ProcessElfCore::AddAddressRangeFromLoadSegment(
       m_core_aranges.Append(range_entry);
     }
   }
-  // Keep a separate map of permissions that isn't coalesced so all ranges
-  // are maintained.
+  // Keep mapped regions separate from m_core_aranges and uncoalesced so each
+  // PT_LOAD's permissions are preserved.
   const uint32_t permissions =
       ((header.p_flags & llvm::ELF::PF_R) ? lldb::ePermissionsReadable : 0u) |
       ((header.p_flags & llvm::ELF::PF_W) ? lldb::ePermissionsWritable : 0u) |
       ((header.p_flags & llvm::ELF::PF_X) ? lldb::ePermissionsExecutable : 0u);
 
-  m_core_range_infos.Append(
-      VMRangeToPermissions::Entry(addr, header.p_memsz, permissions));
+  MemoryRegionInfo region_info;
+  region_info.GetRange() = MemoryRegionInfo::RangeType(addr, header.p_memsz);
+  region_info.SetLLDBPermissions(permissions);
+  region_info.SetMapped(eLazyBoolYes);
+  region_info.SetMemoryTagged(eLazyBoolNo);
+  m_core_range_infos.insert(std::move(region_info));
 
   return addr;
 }
@@ -224,10 +230,11 @@ Status ProcessElfCore::DoLoadCore() {
 
   if (!ranges_are_sorted) {
     m_core_aranges.Sort();
-    m_core_range_infos.Sort();
     m_core_tag_ranges.Sort();
   }
 
+  FinalizeMemoryRegionInfos();
+
   // Ensure we found at least one thread that was stopped on a signal.
   bool siginfo_signal_found = false;
   bool prstatus_signal_found = false;
@@ -316,6 +323,70 @@ void ProcessElfCore::UpdateBuildIdForNTFileEntries() {
   }
 }
 
+void ProcessElfCore::FinalizeMemoryRegionInfos() {
+  std::set<MemoryRegionInfo, std::less<>> finalized_regions;
+  // Add NT_FILE paths as names to PT_LOAD regions with matching start
+  // addresses, preserving the PT_LOAD ranges and permissions.
+  for (MemoryRegionInfo region_info : m_core_range_infos) {
+    const lldb::addr_t range_base = region_info.GetRange().GetRangeBase();
+    const lldb::addr_t range_end = region_info.GetRange().GetRangeEnd();
+
+    auto file_entry =
+        std::find_if(m_nt_file_entries.begin(), m_nt_file_entries.end(),
+                     [range_base](const NT_FILE_Entry &entry) {
+                       return entry.start == range_base;
+                     });
+    if (file_entry != m_nt_file_entries.end() && !file_entry->path.empty())
+      region_info.SetName(file_entry->path.c_str());
+
+    const VMRangeToFileOffset::Entry *tag_entry =
+        m_core_tag_ranges.FindEntryStartsAt(range_base);
+    if (tag_entry && tag_entry->GetRangeEnd() == range_end)
+      region_info.SetMemoryTagged(eLazyBoolYes);
+
+    finalized_regions.insert(std::move(region_info));
+  }
+
+  // Create mapped regions with unknown permissions for portions of NT_FILE
+  // entries not covered by any PT_LOAD region.
+  for (const NT_FILE_Entry &file_entry : m_nt_file_entries) {
+    if (file_entry.start >= file_entry.end)
+      continue;
+
+    lldb::addr_t cursor = file_entry.start;
+    std::vector<MemoryRegionInfo::RangeType> uncovered_ranges;
+    for (const MemoryRegionInfo &region_info : finalized_regions) {
+      const lldb::addr_t range_base = region_info.GetRange().GetRangeBase();
+      const lldb::addr_t range_end = region_info.GetRange().GetRangeEnd();
+
+      if (range_end <= cursor)
+        continue;
+      if (range_base >= file_entry.end)
+        break;
+
+      if (cursor < range_base)
+        uncovered_ranges.emplace_back(cursor, range_base - cursor);
+
+      cursor = std::max(cursor, range_end);
+      if (cursor >= file_entry.end)
+        break;
+    }
+
+    if (cursor < file_entry.end)
+      uncovered_ranges.emplace_back(cursor, file_entry.end - cursor);
+
+    for (const MemoryRegionInfo::RangeType &range : uncovered_ranges) {
+      MemoryRegionInfo region_info;
+      region_info.GetRange() = range;
+      region_info.SetMapped(eLazyBoolYes);
+      if (!file_entry.path.empty())
+        region_info.SetName(file_entry.path.c_str());
+      finalized_regions.insert(std::move(region_info));
+    }
+  }
+  m_core_range_infos = std::move(finalized_regions);
+}
+
 /// Correctly create a FileSpec from a path found in a core file.
 ///
 /// This method will guess the path style more intelligently that specifying
@@ -466,46 +537,23 @@ size_t ProcessElfCore::ReadMemory(lldb::addr_t addr, void *buf, size_t size,
 Status ProcessElfCore::DoGetMemoryRegionInfo(lldb::addr_t load_addr,
                                              MemoryRegionInfo &region_info) {
   region_info.Clear();
-  const VMRangeToPermissions::Entry *permission_entry =
-      m_core_range_infos.FindEntryThatContainsOrFollows(load_addr);
-  if (permission_entry) {
-    if (permission_entry->Contains(load_addr)) {
-      region_info.GetRange().SetRangeBase(permission_entry->GetRangeBase());
-      region_info.GetRange().SetRangeEnd(permission_entry->GetRangeEnd());
-      const Flags permissions(permission_entry->data);
-      region_info.SetReadable(permissions.Test(lldb::ePermissionsReadable)
-                                  ? eLazyBoolYes
-                                  : eLazyBoolNo);
-      region_info.SetWritable(permissions.Test(lldb::ePermissionsWritable)
-                                  ? eLazyBoolYes
-                                  : eLazyBoolNo);
-      region_info.SetExecutable(permissions.Test(lldb::ePermissionsExecutable)
-                                    ? eLazyBoolYes
-                                    : eLazyBoolNo);
-      region_info.SetMapped(eLazyBoolYes);
-
-      // A region is memory tagged if there is a memory tag segment that covers
-      // the exact same range.
-      region_info.SetMemoryTagged(eLazyBoolNo);
-      const VMRangeToFileOffset::Entry *tag_entry =
-          m_core_tag_ranges.FindEntryStartsAt(permission_entry->GetRangeBase());
-      if (tag_entry &&
-          tag_entry->GetRangeEnd() == permission_entry->GetRangeEnd())
-        region_info.SetMemoryTagged(eLazyBoolYes);
-    } else if (load_addr < permission_entry->GetRangeBase()) {
-      region_info.GetRange().SetRangeBase(load_addr);
-      region_info.GetRange().SetRangeEnd(permission_entry->GetRangeBase());
-      region_info.SetReadable(eLazyBoolNo);
-      region_info.SetWritable(eLazyBoolNo);
-      region_info.SetExecutable(eLazyBoolNo);
-      region_info.SetMapped(eLazyBoolNo);
-      region_info.SetMemoryTagged(eLazyBoolNo);
-    }
+  auto following = m_core_range_infos.upper_bound(load_addr);
+  // PT_LOAD ranges can overlap, so the immediate predecessor is not
+  // necessarily the range containing load_addr.
+  auto range_entry = std::find_if(m_core_range_infos.begin(), following,
+                                  [load_addr](const auto &entry) {
+                                    return entry.GetRange().Contains(load_addr);
+                                  });
+  if (range_entry != following) {
+    region_info = *range_entry;
     return Status();
   }
 
   region_info.GetRange().SetRangeBase(load_addr);
-  region_info.GetRange().SetRangeEnd(LLDB_INVALID_ADDRESS);
+  region_info.GetRange().SetRangeEnd(
+      following == m_core_range_infos.end()
+          ? LLDB_INVALID_ADDRESS
+          : following->GetRange().GetRangeBase());
   region_info.SetReadable(eLazyBoolNo);
   region_info.SetWritable(eLazyBoolNo);
   region_info.SetExecutable(eLazyBoolNo);
diff --git a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h
index 846d8cb91cadf..4233a80fa3ac7 100644
--- a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h
+++ b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h
@@ -16,10 +16,13 @@
 #ifndef LLDB_SOURCE_PLUGINS_PROCESS_ELF_CORE_PROCESSELFCORE_H
 #define LLDB_SOURCE_PLUGINS_PROCESS_ELF_CORE_PROCESSELFCORE_H
 
+#include <functional>
 #include <list>
+#include <set>
 #include <unordered_map>
 #include <vector>
 
+#include "lldb/Target/MemoryRegionInfo.h"
 #include "lldb/Target/PostMortemProcess.h"
 #include "lldb/Utility/Args.h"
 #include "lldb/Utility/Status.h"
@@ -124,8 +127,6 @@ class ProcessElfCore : public lldb_private::PostMortemProcess {
   typedef lldb_private::Range<lldb::addr_t, lldb::addr_t> FileRange;
   typedef lldb_private::RangeDataVector<lldb::addr_t, lldb::addr_t, FileRange>
       VMRangeToFileOffset;
-  typedef lldb_private::RangeDataVector<lldb::addr_t, lldb::addr_t, uint32_t>
-      VMRangeToPermissions;
 
   lldb::ModuleSP m_core_module_sp;
   std::string m_dyld_plugin_name;
@@ -142,8 +143,8 @@ class ProcessElfCore : public lldb_private::PostMortemProcess {
   // Address ranges found in the core
   VMRangeToFileOffset m_core_aranges;
 
-  // Permissions for all ranges
-  VMRangeToPermissions m_core_range_infos;
+  // Information for all mapped ranges, ordered by address.
+  std::set<lldb_private::MemoryRegionInfo, std::less<>> m_core_range_infos;
 
   // Memory tag ranges found in the core
   VMRangeToFileOffset m_core_tag_ranges;
@@ -170,6 +171,9 @@ class ProcessElfCore : public lldb_private::PostMortemProcess {
   // Populate gnu uuid for each NT_FILE entry
   void UpdateBuildIdForNTFileEntries();
 
+  // Complete memory region information after all program headers are parsed.
+  void FinalizeMemoryRegionInfos();
+
   bool FindModuleUUID(lldb_private::ModuleSpec &spec) override;
 
   // Extract the executable module spec for the executable in this core file.
diff --git a/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py b/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py
index 2dfba0cd71da1..0bbce8a5dc4a6 100644
--- a/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py
+++ b/lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py
@@ -1444,6 +1444,75 @@ def do_test(self, filename, pid, region_count, thread_name):
 
         self.dbg.DeleteTarget(target)
 
+    @skipIfLLVMTargetMissing("X86")
+    @skipIfWindows
+    def test_memory_region_name_from_nt_file(self):
+        yaml_path = self.getSourcePath("elf-NT_FILE-memory-region.yaml")
+        core_path = self.getBuildArtifact("elf-NT_FILE-memory-region.core")
+        self.yaml2obj(yaml_path, core_path)
+        target = self.dbg.CreateTarget(None)
+        process = target.LoadCore(core_path)
+        self.assertTrue(process.IsValid())
+
+        region = lldb.SBMemoryRegionInfo()
+        self.assertSuccess(process.GetMemoryRegionInfo(0x400000, region))
+        self.assertEqual(region.GetRegionBase(), 0x400000)
+        self.assertEqual(region.GetRegionEnd(), 0x401000)
+        self.assertTrue(region.IsMapped())
+        self.assertTrue(region.IsReadable())
+        self.assertFalse(region.IsWritable())
+        self.assertTrue(region.IsExecutable())
+        self.assertEqual(region.GetName(), "/tmp/kernel.hsaco")
+
+        interior_region = lldb.SBMemoryRegionInfo()
+        self.assertSuccess(process.GetMemoryRegionInfo(0x401000, interior_region))
+        self.assertEqual(interior_region.GetRegionBase(), 0x401000)
+        self.assertEqual(interior_region.GetRegionEnd(), 0x402000)
+        self.assertTrue(interior_region.IsMapped())
+        self.assertTrue(interior_region.IsReadable())
+        self.assertFalse(interior_region.IsWritable())
+        self.assertFalse(interior_region.IsExecutable())
+        self.assertIsNone(interior_region.GetName())
+
+        nt_file_region = lldb.SBMemoryRegionInfo()
+        self.assertSuccess(process.GetMemoryRegionInfo(0x402000, nt_file_region))
+        self.assertEqual(nt_file_region.GetRegionBase(), 0x402000)
+        self.assertEqual(nt_file_region.GetRegionEnd(), 0x403000)
+        self.assertTrue(nt_file_region.IsMapped())
+        # SB's boolean permission accessors report unknown as false.
+        self.assertFalse(nt_file_region.IsReadable())
+        self.assertFalse(nt_file_region.IsWritable())
+        self.assertFalse(nt_file_region.IsExecutable())
+        self.assertEqual(nt_file_region.GetName(), "/tmp/kernel.hsaco")
+
+        self.expect(
+            "memory region 0x402000",
+            substrs=["???", "/tmp/kernel.hsaco"],
+        )
+
+        regions = process.GetMemoryRegions()
+        self.assertEqual(regions.GetSize(), 3)
+        listed_region = lldb.SBMemoryRegionInfo()
+        self.assertTrue(
+            regions.GetMemoryRegionContainingAddress(0x400000, listed_region)
+        )
+        self.assertEqual(listed_region, region)
+        self.assertTrue(
+            regions.GetMemoryRegionContainingAddress(0x401000, listed_region)
+        )
+        self.assertEqual(listed_region, interior_region)
+        self.assertTrue(regions.GetMemoryRegionAtIndex(2, listed_region))
+        self.assertEqual(listed_region, nt_file_region)
+
+        following_region = lldb.SBMemoryRegionInfo()
+        self.assertSuccess(process.GetMemoryRegionInfo(0x403000, following_region))
+        self.assertEqual(following_region.GetRegionBase(), 0x403000)
+        self.assertEqual(following_region.GetRegionEnd(), lldb.LLDB_INVALID_ADDRESS)
+        self.assertFalse(following_region.IsMapped())
+        self.assertIsNone(following_region.GetName())
+
+        self.dbg.DeleteTarget(target)
+
     @skipIfLLVMTargetMissing("X86")
     @skipIfWindows
     def test_exe_name_extraction_nt_file(self):
diff --git a/lldb/test/API/functionalities/postmortem/elf-core/elf-NT_FILE-memory-region.yaml b/lldb/test/API/functionalities/postmortem/elf-core/elf-NT_FILE-memory-region.yaml
new file mode 100644
index 0000000000000..b661f6de87cf0
--- /dev/null
+++ b/lldb/test/API/functionalities/postmortem/elf-core/elf-NT_FILE-memory-region.yaml
@@ -0,0 +1,30 @@
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_CORE
+  Machine: EM_X86_64
+  OSABI:   ELFOSABI_LINUX
+ProgramHeaders:
+  - Type:     PT_LOAD
+    Flags:    [ PF_R, PF_X ]
+    VAddr:    0x400000
+    Align:    0x1000
+    FileSize: 0
+    MemSize:  0x1000
+  - Type:     PT_LOAD
+    Flags:    [ PF_R ]
+    VAddr:    0x401000
+    Align:    0x1000
+    FileSize: 0
+    MemSize:  0x1000
+  - Type:     PT_NOTE
+    FirstSec: .note
+    LastSec:  .note
+Sections:
+  - Name: .note
+    Type: SHT_NOTE
+    Notes:
+      - Name: CORE
+        Type: NT_FILE
+        Desc: 020000000000000000100000000000000000400000000000001040000000000000000000000000000020400000000000003040000000000002000000000000002f746d702f6b65726e656c2e687361636f002f746d702f6b65726e656c2e687361636f00

>From 4e02fa3ff8eea10ae60ab8e79a5777c4e7d560d8 Mon Sep 17 00:00:00 2001
From: Chinmay Deshpande <chdeshpa at amd.com>
Date: Wed, 5 Aug 2026 15:22:24 -0700
Subject: [PATCH 14/18] [AMDGPU][GISel] RegBankLegalize rule for amdgcn_lerp
 (#214324)

---
 .../AMDGPU/AMDGPURegBankLegalizeRules.cpp     |  4 ++
 llvm/test/CodeGen/AMDGPU/llvm.amdgcn.lerp.ll  | 63 ++++++++++++++++++-
 2 files changed, 64 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp b/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp
index 5c276147d0047..46855ba1b3eab 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPURegBankLegalizeRules.cpp
@@ -1963,6 +1963,10 @@ RegBankLegalizeRules::RegBankLegalizeRules(const GCNSubtarget &_ST,
       .Uni(S32, {{UniInVgprS32}, {IntrId, Vgpr32, Vgpr32, Vgpr32}})
       .Div(S32, {{Vgpr32}, {IntrId, Vgpr32, Vgpr32, Vgpr32}});
 
+  addRulesForIOpcs({amdgcn_lerp}, Standard)
+      .Uni(S32, {{UniInVgprS32}, {IntrId, Vgpr32, Vgpr32, Vgpr32}})
+      .Div(S32, {{Vgpr32}, {IntrId, Vgpr32, Vgpr32, Vgpr32}});
+
   addRulesForIOpcs(
       {amdgcn_msad_u8, amdgcn_sad_hi_u8, amdgcn_sad_u16, amdgcn_sad_u8},
       Standard)
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.lerp.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.lerp.ll
index 8a368f69e55c2..57cb71d0328c1 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.lerp.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.lerp.ll
@@ -1,13 +1,70 @@
-; RUN: llc -mtriple=amdgpu8.03 < %s | FileCheck -check-prefix=GCN %s
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -global-isel=0 -mtriple=amdgpu8.03 < %s | FileCheck -check-prefixes=GCN,GCN-SDAG %s
+; RUN: llc -global-isel=1 -mtriple=amdgpu8.03 < %s | FileCheck -check-prefixes=GCN,GCN-GISEL %s
 
 declare i32 @llvm.amdgcn.lerp(i32, i32, i32) #0
 
-; GCN-LABEL: {{^}}v_lerp:
-; GCN: v_lerp_u8 v{{[0-9]+}}, v{{[0-9]+}}, s{{[0-9]+}}, s{{[0-9]+}}
 define amdgpu_kernel void @v_lerp(ptr addrspace(1) %out, i32 %src) nounwind {
+; GCN-SDAG-LABEL: v_lerp:
+; GCN-SDAG:       ; %bb.0:
+; GCN-SDAG-NEXT:    s_load_dword s2, s[4:5], 0x2c
+; GCN-SDAG-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x24
+; GCN-SDAG-NEXT:    s_movk_i32 s3, 0x64
+; GCN-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; GCN-SDAG-NEXT:    v_mov_b32_e32 v0, s2
+; GCN-SDAG-NEXT:    v_lerp_u8 v2, v0, s3, s3
+; GCN-SDAG-NEXT:    v_mov_b32_e32 v0, s0
+; GCN-SDAG-NEXT:    v_mov_b32_e32 v1, s1
+; GCN-SDAG-NEXT:    flat_store_dword v[0:1], v2
+; GCN-SDAG-NEXT:    s_endpgm
+;
+; GCN-GISEL-LABEL: v_lerp:
+; GCN-GISEL:       ; %bb.0:
+; GCN-GISEL-NEXT:    s_load_dword s2, s[4:5], 0x2c
+; GCN-GISEL-NEXT:    s_load_dwordx2 s[0:1], s[4:5], 0x24
+; GCN-GISEL-NEXT:    v_mov_b32_e32 v0, 0x64
+; GCN-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; GCN-GISEL-NEXT:    v_lerp_u8 v2, s2, v0, v0
+; GCN-GISEL-NEXT:    v_mov_b32_e32 v0, s0
+; GCN-GISEL-NEXT:    v_mov_b32_e32 v1, s1
+; GCN-GISEL-NEXT:    flat_store_dword v[0:1], v2
+; GCN-GISEL-NEXT:    s_endpgm
   %result= call i32 @llvm.amdgcn.lerp(i32 %src, i32 100, i32 100) #0
   store i32 %result, ptr addrspace(1) %out, align 4
   ret void
 }
 
+define amdgpu_kernel void @v_lerp_non_immediate(ptr addrspace(1) %out, i32 %src, i32 %a, i32 %b) nounwind {
+; GCN-SDAG-LABEL: v_lerp_non_immediate:
+; GCN-SDAG:       ; %bb.0:
+; GCN-SDAG-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x2c
+; GCN-SDAG-NEXT:    s_load_dwordx2 s[4:5], s[4:5], 0x24
+; GCN-SDAG-NEXT:    s_waitcnt lgkmcnt(0)
+; GCN-SDAG-NEXT:    v_mov_b32_e32 v0, s1
+; GCN-SDAG-NEXT:    v_mov_b32_e32 v1, s2
+; GCN-SDAG-NEXT:    v_lerp_u8 v2, s0, v0, v1
+; GCN-SDAG-NEXT:    v_mov_b32_e32 v0, s4
+; GCN-SDAG-NEXT:    v_mov_b32_e32 v1, s5
+; GCN-SDAG-NEXT:    flat_store_dword v[0:1], v2
+; GCN-SDAG-NEXT:    s_endpgm
+;
+; GCN-GISEL-LABEL: v_lerp_non_immediate:
+; GCN-GISEL:       ; %bb.0:
+; GCN-GISEL-NEXT:    s_load_dwordx4 s[0:3], s[4:5], 0x24
+; GCN-GISEL-NEXT:    s_load_dword s4, s[4:5], 0x34
+; GCN-GISEL-NEXT:    s_waitcnt lgkmcnt(0)
+; GCN-GISEL-NEXT:    v_mov_b32_e32 v0, s3
+; GCN-GISEL-NEXT:    v_mov_b32_e32 v1, s4
+; GCN-GISEL-NEXT:    v_lerp_u8 v2, s2, v0, v1
+; GCN-GISEL-NEXT:    v_mov_b32_e32 v0, s0
+; GCN-GISEL-NEXT:    v_mov_b32_e32 v1, s1
+; GCN-GISEL-NEXT:    flat_store_dword v[0:1], v2
+; GCN-GISEL-NEXT:    s_endpgm
+  %result= call i32 @llvm.amdgcn.lerp(i32 %src, i32 %a, i32 %b) #0
+  store i32 %result, ptr addrspace(1) %out, align 4
+  ret void
+}
+
 attributes #0 = { nounwind readnone }
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; GCN: {{.*}}

>From f6b7fdbef64898c6169fd201b0135fd778cc82b9 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Wed, 5 Aug 2026 17:50:00 -0500
Subject: [PATCH 15/18] [CIR] Accept unions in x86_64 calling-convention
 lowering (#214129)

Passing a union to a function does not work. The x86_64 bridge rejects
every one, so the pass fails on any signature naming a union.
Additionally, indirect arguments have their byval and sret alignment
wrong, because `mapCIRType` asks DataLayout for it, and DataLayout only
sees a record's members, never `__attribute__((aligned(N)))`.

Unions now go through the ABI library's union type, which puts every
member at offset zero and sizes each eightbyte from the union rather
than from a single member. The alignment comes from the record-layout
metadata the AST already fills in, which fixes over-aligned structs too,
since they share that lookup.

Assisted-by: Cursor / claude-opus-5
---
 .../include/clang/CIR/Dialect/IR/CIRDialect.h |   6 +-
 clang/lib/CIR/Dialect/IR/CIRAttrs.cpp         |  18 +-
 .../Transforms/CallConvLoweringPass.cpp       | 123 +++++---
 .../CIR/CodeGen/call-conv-lowering-x86_64.c   |  84 ++++++
 .../abi-lowering/x86_64-aggregate-nyi.cir     |  82 ++++-
 .../x86_64-union-coerce-shapes.cir            |  80 +++++
 .../Transforms/abi-lowering/x86_64-union.cir  | 280 ++++++++++++++++++
 7 files changed, 618 insertions(+), 55 deletions(-)
 create mode 100644 clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir
 create mode 100644 clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
index c6f6c80206bfe..2f1ef5b6cb9e0 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
@@ -83,7 +83,11 @@ class FenvOpTrait : public mlir::OpTrait::TraitBase<ConcreteType, FenvOpTrait> {
 
 /// Look up the RecordLayoutAttr for a named record in the module's
 /// cir.record_layouts dictionary.  Asserts if the entry is missing.
-RecordLayoutAttr getRecordLayout(mlir::ModuleOp module, mlir::StringAttr name);
+RecordLayoutAttr getRecordLayout(mlir::ModuleOp mod, mlir::StringAttr name);
+
+/// Same lookup as getRecordLayout, but returns a null attribute instead of
+/// asserting when the record has no layout entry.
+RecordLayoutAttr tryGetRecordLayout(mlir::ModuleOp mod, mlir::StringAttr name);
 } // namespace cir
 
 // TableGen'erated files for MLIR dialects require that a macro be defined when
diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
index 264e836718c81..99da5044751f0 100644
--- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
@@ -920,12 +920,20 @@ LogicalResult DynamicCastInfoAttr::verify(
 // RecordLayout lookup
 //===----------------------------------------------------------------------===//
 
-RecordLayoutAttr cir::getRecordLayout(mlir::ModuleOp module,
-                                      mlir::StringAttr name) {
-  auto dict = module->getAttrOfType<mlir::DictionaryAttr>(
+RecordLayoutAttr cir::tryGetRecordLayout(mlir::ModuleOp mod,
+                                         mlir::StringAttr name) {
+  if (!name)
+    return {};
+  auto dict = mod->getAttrOfType<mlir::DictionaryAttr>(
       CIRDialect::getRecordLayoutsAttrName());
-  assert(dict && "module missing cir.record_layouts attribute");
-  auto attr = dict.getAs<RecordLayoutAttr>(name);
+  if (!dict)
+    return {};
+  return dict.getAs<RecordLayoutAttr>(name);
+}
+
+RecordLayoutAttr cir::getRecordLayout(mlir::ModuleOp mod,
+                                      mlir::StringAttr name) {
+  RecordLayoutAttr attr = tryGetRecordLayout(mod, name);
   assert(attr && "record layout entry missing for named record");
   return attr;
 }
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 810b4b20b82db..193c2b6f4a9dc 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -68,10 +68,11 @@ namespace {
 // SysV x86_64 classifier, and converts the result back into the
 // dialect-agnostic mlir::abi::FunctionClassification that CIRABIRewriteContext
 // consumes.  Integer (including `_BitInt` up to 128 bits) / pointer / bool /
-// f32 / f64 scalars and struct / array aggregates are handled.  Unions,
-// `_Complex`, vectors, wider floats, and packed or padded records are reported
-// NYI by classifyX86_64Function so an unsupported signature fails the pass
-// instead of being misclassified.
+// f32 / f64 scalars and struct / union / array aggregates are handled.
+// `_Complex`, vectors, wider floats, packed or padded records, and a union no
+// member of which spans its declared size are reported NYI by
+// classifyX86_64Function so an unsupported signature fails the pass instead of
+// being misclassified.
 //===----------------------------------------------------------------------===//
 
 /// Whether a struct's declared argument-passing kind (from the module's
@@ -79,27 +80,32 @@ namespace {
 /// no layout entry (e.g. an anonymous struct) has no C++ non-trivial reason to
 /// be forced to memory, so it defaults to can-pass-in-registers.
 static bool recordCanPassInRegs(ModuleOp modOp, cir::RecordType recTy) {
-  mlir::StringAttr name = recTy.getName();
-  if (!name)
-    return true;
-  auto dict = modOp->getAttrOfType<DictionaryAttr>(
-      cir::CIRDialect::getRecordLayoutsAttrName());
-  if (!dict)
-    return true;
-  auto layout = dict.getAs<cir::RecordLayoutAttr>(name);
+  auto layout = cir::tryGetRecordLayout(modOp, recTy.getName());
   if (!layout)
     return true;
   return layout.getArgPassingKind() == cir::ArgPassingKind::CanPassInRegs;
 }
 
+/// A record's declared alignment, which the ABI uses for the byval and sret
+/// alignment of an indirect argument.  DataLayout derives alignment from the
+/// members, so it cannot see `__attribute__((aligned(N)))`.  The declared value
+/// comes from the module's record-layout metadata instead.  CIRGen emits an
+/// entry for every record it names, so the computed fallback only serves
+/// hand-written CIR.
+static llvm::Align recordDeclaredAlign(ModuleOp modOp, cir::RecordType recTy,
+                                       const DataLayout &dl) {
+  auto layout = cir::tryGetRecordLayout(modOp, recTy.getName());
+  if (!layout)
+    return llvm::Align(dl.getTypeABIAlignment(recTy));
+  return llvm::Align(layout.getRecordAlign());
+}
+
 /// The CIR types the x86_64 bridge handles.  Scalars: an integer up to 128
 /// bits (including `_BitInt` and `__int128`), pointer, bool, void, f32, or f64.
-/// Aggregates: a complete struct whose fields are all themselves supported, or
-/// an array of a supported element type.  A `_BitInt` wider than 128 bits,
-/// unions, `_Complex`, vectors, wider floats, and packed or padded records are
-/// not handled and are reported NYI at the reject() choke point in
-/// classifyX86_64Function.
-static bool isSupportedType(mlir::Type ty) {
+/// Aggregates: a complete struct or union whose members are all themselves
+/// supported, or an array of a supported element type.  Everything else is
+/// reported NYI at the reject() choke point in classifyX86_64Function.
+static bool isSupportedType(mlir::Type ty, const DataLayout &dl) {
   // A pointer is only handled in the default address space (null) or an
   // already-lowered target address space.  A LangAddressSpaceAttr must be
   // lowered before this pass, so reject it rather than silently dropping it.
@@ -124,21 +130,40 @@ static bool isSupportedType(mlir::Type ty) {
     return intTy.getWidth() <= 64 || intTy.getWidth() == 128;
   }
   if (auto arrTy = dyn_cast<cir::ArrayType>(ty))
-    return isSupportedType(arrTy.getElementType());
+    return isSupportedType(arrTy.getElementType(), dl);
   if (auto recTy = dyn_cast<cir::RecordType>(ty)) {
-    // Unions and packed / padded records each need classification this bridge
-    // does not implement (a union widen fixup and pad-aware eightbyte
-    // classification), so reject them here and report NYI rather than
-    // misclassify.  A zero-field record (a C empty struct) classifies as
-    // Ignore and is dropped from the lowered signature.  CIRGen lays out an
-    // empty C++ class as a single padded byte, which the padded check rejects.
-    // A real one-byte struct such as `{char[1]}` has a field and is not
-    // padded, so it is classified normally.
-    if (recTy.isUnion() || !recTy.isComplete() || recTy.getPacked() ||
-        recTy.getPadded())
+    // An incomplete record has no layout to classify, and a packed one needs
+    // pad-aware eightbyte classification this bridge does not implement.
+    if (!recTy.isComplete() || recTy.getPacked())
       return false;
+    if (recTy.isUnion()) {
+      // The classifier sizes a union's eightbytes from the union itself, which
+      // is only sound when some member spans that size.  Short of that, the
+      // remaining bytes are either tail padding or the rest of a bitfield
+      // storage unit, and the CIR type cannot tell those apart even though
+      // classic CodeGen coerces them to i32 and i8 respectively.
+      llvm::ArrayRef<mlir::Type> members = recTy.getMembers();
+      uint64_t recordBits = dl.getTypeSizeInBits(recTy).getFixedValue();
+      if (members.empty()) {
+        // A member-less union is all padding, which classifies Ignore up to two
+        // eightbytes.  Past that SysV says MEMORY regardless of content, and
+        // there is no member here to build the Indirect coercion from.
+        if (recordBits > 128)
+          return false;
+      } else {
+        auto spansRecord = [&](mlir::Type m) {
+          return dl.getTypeSizeInBits(m).getFixedValue() == recordBits;
+        };
+        if (!llvm::any_of(members, spansRecord))
+          return false;
+      }
+    } else if (recTy.getPadded()) {
+      // A struct's padding is a member the classifier would have to recognize
+      // as padding rather than data, which is not implemented.
+      return false;
+    }
     return llvm::all_of(recTy.getMembers(),
-                        [](mlir::Type m) { return isSupportedType(m); });
+                        [&](mlir::Type m) { return isSupportedType(m, dl); });
   }
   return false;
 }
@@ -220,11 +245,28 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
                                dl.getTypeSizeInBits(type).getFixedValue());
       })
       .Case([&](cir::RecordType recTy) -> const llvm::abi::Type * {
-        // isSupportedType rejects unions, packed / padded, and empty-for-ABI
-        // records, so this handles a plain struct: map each field at its
-        // naturally-aligned offset.
+        llvm::abi::RecordFlags flags = llvm::abi::RecordFlags::None;
+        if (recordCanPassInRegs(modOp, recTy))
+          flags = flags | llvm::abi::RecordFlags::CanPassInRegisters;
+        llvm::TypeSize sizeBits = llvm::TypeSize::getFixed(
+            dl.getTypeSizeInBits(type).getFixedValue());
+        llvm::Align align = recordDeclaredAlign(modOp, recTy, dl);
         SmallVector<llvm::abi::FieldInfo> fields;
         fields.reserve(recTy.getMembers().size());
+
+        // The size passed here spans the tail padding, so an eightbyte covers
+        // the whole union rather than just the member the classifier reduces
+        // it to.
+        if (recTy.isUnion()) {
+          for (mlir::Type fieldTy : recTy.getMembers())
+            fields.push_back(llvm::abi::FieldInfo(
+                mapCIRType(fieldTy, typeMapper, dl, modOp)));
+          return tb.getUnionType(fields, sizeBits, align,
+                                 llvm::abi::StructPacking::Default, flags);
+        }
+
+        // isSupportedType rejects packed and padded structs, so every field
+        // here sits at its naturally-aligned offset.
         uint64_t offsetBits = 0;
         for (mlir::Type fieldTy : recTy.getMembers()) {
           const llvm::abi::Type *mappedField =
@@ -234,16 +276,9 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
           fields.push_back(llvm::abi::FieldInfo(mappedField, offsetBits));
           offsetBits += dl.getTypeSizeInBits(fieldTy).getFixedValue();
         }
-        llvm::abi::RecordFlags flags = llvm::abi::RecordFlags::None;
-        if (recordCanPassInRegs(modOp, recTy))
-          flags = flags | llvm::abi::RecordFlags::CanPassInRegisters;
-        return tb.getRecordType(fields,
-                                llvm::TypeSize::getFixed(
-                                    dl.getTypeSizeInBits(type).getFixedValue()),
-                                llvm::Align(dl.getTypeABIAlignment(type)),
-                                llvm::abi::StructPacking::Default,
-                                /*BaseClasses=*/{}, /*VirtualBaseClasses=*/{},
-                                flags);
+        return tb.getRecordType(
+            fields, sizeBits, align, llvm::abi::StructPacking::Default,
+            /*BaseClasses=*/{}, /*VirtualBaseClasses=*/{}, flags);
       })
       .Default([](mlir::Type) -> const llvm::abi::Type * {
         llvm_unreachable(
@@ -353,7 +388,7 @@ static std::optional<FunctionClassification> classifyX86_64Signature(
   bool voidRet = isa<cir::VoidType>(retCIR);
 
   auto reject = [&](mlir::Type t) -> bool {
-    if (isSupportedType(t))
+    if (isSupportedType(t, dl))
       return false;
     emitError()
         << "x86_64 calling-convention lowering not yet implemented for type "
diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c
index 6931198a90c3d..8382b15bb5d9e 100644
--- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c
@@ -11,6 +11,11 @@ typedef struct { long a, b, c, d; } Big;
 typedef struct { long a; double b; } IntSSE;
 typedef struct { double a; double b; } SSE2;
 typedef struct { } Empty;
+typedef union { int i; float f; } UIntFloat;
+typedef union { float f; float g; } UFloats;
+typedef union { int i; char c[8]; } UNarrowStorage;
+typedef union { char c[32]; } UBig;
+typedef union { char c[32]; } __attribute__((aligned(32))) UBigOverAligned;
 
 // Narrow signed integer sign-extended in a register.
 signed char ext_schar(signed char c) { return c; }
@@ -79,3 +84,82 @@ void take_big(Big b) { (void)b; }
 // CIR: cir.func {{.*}}@take_big(%arg0: !cir.ptr<!rec_Big> {{.*}}llvm.byval = !rec_Big{{.*}})
 // LLVM-CIR: define dso_local void @take_big(ptr noalias noundef byval(%struct.Big) align 8 %{{.+}})
 // LLVM-OGCG: define dso_local void @take_big(ptr noundef byval(%struct.Big) align 8 %{{.+}})
+
+// Union members all start at offset zero, so a 4-byte union takes one INTEGER
+// eightbyte and coerces to i32.
+void take_union(UIntFloat u) { (void)u; }
+
+// CIR: cir.func {{.*}}@take_union(%arg0: !s32i{{.*}})
+// LLVM: define dso_local void @take_union(i32 %{{.+}})
+
+// A union of floats classifies SSE, so it coerces to a float register.
+void take_union_floats(UFloats u) { (void)u; }
+
+// CIR: cir.func {{.*}}@take_union_floats(%arg0: !cir.float{{.*}})
+// LLVM: define dso_local void @take_union_floats(float %{{.+}})
+
+// The union's highest-aligned member is the 4-byte int, but its size comes
+// from the 8-byte array, and the eightbyte is sized from the union.
+void take_union_narrow_storage(UNarrowStorage u) { (void)u; }
+
+// CIR: cir.func {{.*}}@take_union_narrow_storage(%arg0: !u64i{{.*}})
+// LLVM: define dso_local void @take_union_narrow_storage(i64 %{{.+}})
+
+// A coerced union return round-trips through the coercion type.
+UIntFloat ret_union(int a) { UIntFloat u; u.i = a; return u; }
+
+// CIR: cir.func {{.*}}@ret_union(%arg0: !s32i {{.*}}) -> !s32i
+// LLVM: define dso_local i32 @ret_union(i32 noundef %{{.+}})
+
+// A union too large for registers is passed byval, with the same noalias
+// divergence as a large struct.
+void take_union_big(UBig u) { (void)u; }
+
+// CIR: cir.func {{.*}}@take_union_big(%arg0: !cir.ptr<!rec_UBig> {{.*}}llvm.byval = !rec_UBig{{.*}})
+// LLVM-CIR: define dso_local void @take_union_big(ptr noalias noundef byval(%union.UBig) align 8 %{{.+}})
+// LLVM-OGCG: define dso_local void @take_union_big(ptr noundef byval(%union.UBig) align 8 %{{.+}})
+
+// The byval alignment follows the union's declared alignment, not the alignment
+// its members imply, which is 1 here.
+void take_union_big_over_aligned(UBigOverAligned u) { (void)u; }
+
+// CIR: cir.func {{.*}}@take_union_big_over_aligned(%arg0: !cir.ptr<!rec_UBigOverAligned> {{.*}}llvm.align = 32 : i64{{.*}}llvm.byval = !rec_UBigOverAligned{{.*}})
+// LLVM-CIR: define dso_local void @take_union_big_over_aligned(ptr noalias noundef byval(%union.UBigOverAligned) align 32 %{{.+}})
+// LLVM-OGCG: define dso_local void @take_union_big_over_aligned(ptr noundef byval(%union.UBigOverAligned) align 32 %{{.+}})
+
+void call_union(UIntFloat u) { take_union(u); }
+
+// CIR: cir.func {{.*}}@call_union(%arg0: !s32i
+// CIR:   cir.call @take_union(%{{.+}}) : (!s32i) -> ()
+// LLVM: define dso_local void @call_union(i32 %{{.+}})
+// LLVM:   call void @take_union(i32 %{{.+}})
+
+void call_union_big_over_aligned(UBigOverAligned u) {
+  take_union_big_over_aligned(u);
+}
+
+// CIR: cir.func {{.*}}@call_union_big_over_aligned(%arg0: !cir.ptr<!rec_UBigOverAligned> {{.*}}llvm.align = 32 : i64{{.*}})
+// CIR:   cir.call @take_union_big_over_aligned(%{{.+}}) : (!cir.ptr<!rec_UBigOverAligned> {{.*}}llvm.align = 32 : i64{{.*}}) -> ()
+// LLVM-CIR: define dso_local void @call_union_big_over_aligned(ptr noalias noundef byval(%union.UBigOverAligned) align 32 %{{.+}})
+// LLVM-CIR:   alloca %union.UBigOverAligned, i64 1, align 32
+// LLVM-CIR:   call void @take_union_big_over_aligned(ptr noalias noundef byval(%union.UBigOverAligned) align 32 %{{.+}})
+// LLVM-OGCG: define dso_local void @call_union_big_over_aligned(ptr noundef byval(%union.UBigOverAligned) align 32 %{{.+}})
+// LLVM-OGCG:   call void @take_union_big_over_aligned(ptr noundef byval(%union.UBigOverAligned) align 32 %{{.+}})
+
+// The declared alignment reaches the sret slot of an indirect return too, not
+// just a byval argument.
+UBigOverAligned ret_union_big_over_aligned(void);
+void call_ret_union_big_over_aligned(void) { (void)ret_union_big_over_aligned(); }
+
+// CIR: cir.func {{.*}}@ret_union_big_over_aligned(!cir.ptr<!rec_UBigOverAligned> {{.*}}llvm.align = 32 : i64{{.*}}llvm.sret = !rec_UBigOverAligned{{.*}})
+// LLVM: declare void @ret_union_big_over_aligned(ptr dead_on_unwind writable sret(%union.UBigOverAligned) align 32)
+
+// The same declared-alignment source feeds an over-aligned struct, since
+// mapCIRType's alignment lookup is on the shared record path, not a
+// union-specific one.
+typedef struct { char c[32]; } __attribute__((aligned(32))) SOverAligned;
+void take_struct_over_aligned(SOverAligned s) { (void)s; }
+
+// CIR: cir.func {{.*}}@take_struct_over_aligned(%arg0: !cir.ptr<!rec_SOverAligned> {{.*}}llvm.align = 32 : i64{{.*}}llvm.byval = !rec_SOverAligned{{.*}})
+// LLVM-CIR: define dso_local void @take_struct_over_aligned(ptr noalias noundef byval(%struct.SOverAligned) align 32 %{{.+}})
+// LLVM-OGCG: define dso_local void @take_struct_over_aligned(ptr noundef byval(%struct.SOverAligned) align 32 %{{.+}})
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
index 85f05fef9f96b..83d36ce22b970 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
@@ -1,10 +1,17 @@
 // RUN: not cir-opt %s -cir-call-conv-lowering=target=x86_64 2>&1 | FileCheck %s
 
 !s8i = !cir.int<s, 8>
+!s16i = !cir.int<s, 16>
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
-!u32i = !cir.int<u, 32>
-!rec_U = !cir.union<"U" {!s32i, !u32i}>
+!rec_UPacked = !cir.union<"UPacked" packed {!s32i, !cir.array<!s8i x 5>}, padding = {!u8i}>
+!rec_ULongDouble = !cir.union<"ULongDouble" {!cir.long_double<!cir.f80>, !s32i}>
+!rec_UFloats = !cir.union<"UFloats" {!cir.array<!cir.float x 2>, !cir.array<!cir.float x 2>}>
+!rec_UOverAligned = !cir.union<"UOverAligned" {!s32i}, padding = {!cir.array<!u8i x 12>}>
+!rec_UShortStorage = !cir.union<"UShortStorage" {!s16i, !cir.array<!s8i x 3>}, padding = {!cir.array<!u8i x 2>}>
+!rec_UByteBlobs = !cir.union<"UByteBlobs" {!u8i, !u8i}, padding = {!cir.array<!u8i x 3>}>
+!rec_SWrapsOverAligned = !cir.struct<"SWrapsOverAligned" {!cir.double, !rec_UOverAligned}>
+!rec_UEmptyLarge = !cir.union<"UEmptyLarge" {}, padding = {!cir.array<!u8i x 32>}>
 !rec_P = !cir.struct<"P" packed {!s8i, !s32i}>
 !rec_Ov = !cir.struct<"Ov" padded {!s32i, !cir.array<!u8i x 12>}>
 !rec_E = !cir.struct<"E" padded {!u8i}>
@@ -19,12 +26,77 @@ module attributes {
     #dlti.dl_entry<f64, dense<64>: vector<2xi64>>>
 } {
 
-  // A union is rejected: its register coercion needs a widen fixup.
-  cir.func @take_union(%arg0: !rec_U) {
+  // A packed union is rejected for the same reason a packed struct is: its
+  // members no longer sit at their natural alignment.
+  cir.func @take_packed_union(%arg0: !rec_UPacked) {
     cir.return
   }
 
-  // CHECK: not yet implemented for type '!cir.union<"U"
+  // CHECK: not yet implemented for type '!cir.union<"UPacked" packed
+
+  // A union member the bridge does not map keeps the whole union unsupported.
+  cir.func @take_union_long_double(%arg0: !rec_ULongDouble) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"ULongDouble"
+
+  // A union whose highest-aligned member is an all-float array classifies to
+  // an SSE vector coerce this bridge does not represent, so it is reported NYI
+  // rather than passed unchanged.
+  cir.func @take_union_float_arrays(%arg0: !rec_UFloats) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for the ABI coercion of type '!cir.union<"UFloats"
+
+  // No member of this union spans its 16-byte declared size, so the bytes past
+  // the int cannot be told apart from the rest of a wider storage unit, and the
+  // eightbyte the classifier would build from the union's size is a guess.
+  cir.func @take_over_aligned_union(%arg0: !rec_UOverAligned) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"UOverAligned"
+
+  // Same rule one eightbyte down: the widest member covers 3 of the union's 4
+  // declared bytes.
+  cir.func @take_short_storage_union(%arg0: !rec_UShortStorage) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"UShortStorage"
+
+  // This is the shape CIRGen produces for a union of narrow bitfields, where
+  // the byte-sized members understate a wider storage unit that is all user
+  // data.  It is also the shape of a union of two `unsigned char` members
+  // carrying an alignment attribute, where the same bytes really are padding.
+  // The two coerce differently in classic CodeGen and are identical here, which
+  // is why neither is accepted.
+  cir.func @take_byte_blob_union(%arg0: !rec_UByteBlobs) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"UByteBlobs"
+
+  // The reject propagates out of an enclosing struct rather than being silently
+  // dropped at the member level.
+  cir.func @take_struct_wrapping_over_aligned(%arg0: !rec_SWrapsOverAligned) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.struct<"SWrapsOverAligned"
+
+  // A member-less union past two eightbytes still classifies Indirect in
+  // classic CodeGen (SysV's MEMORY rule applies unconditionally above that
+  // size, regardless of content), but this bridge has no member to build an
+  // Indirect coercion from.  Below the threshold the same shape is accepted
+  // and classifies Ignore, matching classic; see x86_64-union.cir take_empty.
+  cir.func @take_empty_large_union(%arg0: !rec_UEmptyLarge) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"UEmptyLarge"
 
   // A packed struct is rejected: it needs pad-aware classification.
   cir.func @take_packed(%arg0: !rec_P) {
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir
new file mode 100644
index 0000000000000..e7cc09d739de1
--- /dev/null
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir
@@ -0,0 +1,80 @@
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 | FileCheck %s
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 -cir-to-llvm -o - 2>/dev/null \
+// RUN:   | mlir-translate -mlir-to-llvmir --allow-unregistered-dialect \
+// RUN:   | FileCheck %s --check-prefix=LLVM
+
+!s8i = !cir.int<s, 8>
+!u8i = !cir.int<u, 8>
+!s32i = !cir.int<s, 32>
+!s64i = !cir.int<s, 64>
+!s128i = !cir.int<s, 128>
+!rec_U128 = !cir.union<"U128" {!s128i, !cir.array<!s8i x 16>}>
+!rec_UMixed = !cir.union<"UMixed" {!cir.array<!cir.double x 2>, !s64i}>
+!rec_UD2 = !cir.union<"UD2" {!cir.array<!cir.double x 2>}>
+!rec_U12 = !cir.union<"U12" {!cir.array<!s8i x 12>, !s32i}, padding = {!cir.array<!u8i x 8>}>
+!rec_UEmpty16 = !cir.union<"UEmpty16" {}, padding = {!cir.array<!u8i x 16>}>
+
+module attributes {
+  cir.triple = "x86_64-unknown-linux-gnu",
+  dlti.dl_spec = #dlti.dl_spec<
+    #dlti.dl_entry<i8, dense<8>: vector<2xi64>>,
+    #dlti.dl_entry<i64, dense<64>: vector<2xi64>>,
+    #dlti.dl_entry<i128, dense<128>: vector<2xi64>>,
+    #dlti.dl_entry<f64, dense<64>: vector<2xi64>>>
+} {
+
+  // A 16-byte union whose spanning member is one 128-bit integer coerces to that
+  // integer rather than to a pair of eightbytes.
+  cir.func @take_u128(%arg0: !rec_U128) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_u128(%arg0: !s128i)
+
+  // The two eightbytes need not share a class.  The first covers half of the
+  // double array and merges to INTEGER against the long, while the second is all
+  // double and stays SSE.
+  cir.func @take_umixed(%arg0: !rec_UMixed) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_umixed(%arg0: !u64i, %arg1: !cir.double)
+
+  // With no integer member to merge against, both eightbytes stay SSE.
+  cir.func @take_ud2(%arg0: !rec_UD2) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_ud2(%arg0: !cir.double, %arg1: !cir.double)
+
+  // The second eightbyte is partial.  The classifier reduces this union to its
+  // 4-byte integer, so an eightbyte sized from that member would cover only the
+  // first four bytes and drop the rest of the array.
+  cir.func @take_u12(%arg0: !rec_U12) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_u12(%arg0: !u64i, %arg1: !u32i)
+
+  // Two eightbytes in return position, flattened to an anonymous struct.
+  cir.func @ret_umixed(%arg0: !rec_UMixed) -> !rec_UMixed {
+    cir.return %arg0 : !rec_UMixed
+  }
+
+  // CHECK: cir.func{{.*}} @ret_umixed(%arg0: !u64i, %arg1: !cir.double) -> !rec_anon_struct1
+
+  // A member-less union at exactly two eightbytes still classifies Ignore.  One
+  // byte more is MEMORY, which x86_64-aggregate-nyi.cir covers.
+  cir.func @take_empty16(%arg0: !rec_UEmpty16) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty16()
+}
+
+// LLVM: define void @take_u128(i128 %{{.+}})
+// LLVM: define void @take_umixed(i64 %{{.+}}, double %{{.+}})
+// LLVM: define void @take_ud2(double %{{.+}}, double %{{.+}})
+// LLVM: define void @take_u12(i64 %{{.+}}, i32 %{{.+}})
+// LLVM: define { i64, double } @ret_umixed(i64 %{{.+}}, double %{{.+}})
+// LLVM: define void @take_empty16()
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir
new file mode 100644
index 0000000000000..cad6cfd37d7a4
--- /dev/null
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir
@@ -0,0 +1,280 @@
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 | FileCheck %s
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 -cir-to-llvm -o - 2>/dev/null \
+// RUN:   | mlir-translate -mlir-to-llvmir --allow-unregistered-dialect \
+// RUN:   | FileCheck %s --check-prefix=LLVM
+
+!s8i = !cir.int<s, 8>
+!s32i = !cir.int<s, 32>
+!s64i = !cir.int<s, 64>
+!u8i = !cir.int<u, 8>
+!rec_UIntFloat = !cir.union<"UIntFloat" {!s32i, !cir.float}>
+!rec_UFloatInt = !cir.union<"UFloatInt" {!cir.float, !s32i}>
+!rec_ULongDouble = !cir.union<"ULongDouble" {!s64i, !cir.double}>
+!rec_UDoubleLong = !cir.union<"UDoubleLong" {!cir.double, !s64i}>
+!rec_UFloats = !cir.union<"UFloats" {!cir.float, !cir.float}>
+!rec_UThree = !cir.union<"UThree" {!cir.array<!s8i x 3>}>
+!rec_UNarrowStorage = !cir.union<"UNarrowStorage" {!s32i, !cir.array<!s8i x 8>}, padding = {!cir.array<!u8i x 4>}>
+!rec_UTwoEightbytes = !cir.union<"UTwoEightbytes" {!s64i, !cir.array<!s8i x 16>}, padding = {!cir.array<!u8i x 8>}>
+!rec_UBig = !cir.union<"UBig" {!cir.array<!s8i x 32>}>
+!rec_UBigOverAligned = !cir.union<"UBigOverAligned" {!cir.array<!s8i x 32>}>
+!rec_SOverAligned = !cir.struct<"SOverAligned" {!cir.array<!s8i x 32>}>
+!rec_UEmpty = !cir.union<"UEmpty" {}, padding = {!u8i}>
+!rec_UNoRegs = !cir.union<"UNoRegs" {!s32i, !cir.float}>
+!rec_SWithUnion = !cir.struct<"SWithUnion" {!rec_UIntFloat, !s32i}>
+
+module attributes {
+  cir.triple = "x86_64-unknown-linux-gnu",
+  cir.record_layouts = {
+    UNoRegs = #cir.record_layout<
+      arg_passing_kind = cannot_pass_in_regs, has_trivial_dtor = false,
+      record_align = 4>,
+    UBigOverAligned = #cir.record_layout<
+      arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
+      record_align = 32>,
+    SOverAligned = #cir.record_layout<
+      arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
+      record_align = 32>},
+  dlti.dl_spec = #dlti.dl_spec<
+    #dlti.dl_entry<i8, dense<8>: vector<2xi64>>,
+    #dlti.dl_entry<i16, dense<16>: vector<2xi64>>,
+    #dlti.dl_entry<i32, dense<32>: vector<2xi64>>,
+    #dlti.dl_entry<i64, dense<64>: vector<2xi64>>,
+    #dlti.dl_entry<f32, dense<32>: vector<2xi64>>,
+    #dlti.dl_entry<f64, dense<64>: vector<2xi64>>>
+} {
+
+  // Every union member sits at offset zero, so a 4-byte union of an int and a
+  // float classifies INTEGER on its single eightbyte and coerces to i32.
+  cir.func @take_int_float(%arg0: !rec_UIntFloat) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_int_float(%arg0: !s32i)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" align(4) : !cir.ptr<!s32i>
+  // CHECK:   cir.store %arg0, %[[SLOT]] : !s32i, !cir.ptr<!s32i>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!s32i> -> !cir.ptr<!rec_UIntFloat>
+  // CHECK:   %{{.*}} = cir.load %[[CAST]] : !cir.ptr<!rec_UIntFloat>, !rec_UIntFloat
+
+  // Same members as take_int_float in the opposite declaration order.  The
+  // classifier merges eightbyte classes across every member (INTEGER beats
+  // SSE), so which member is listed first does not change the class: this
+  // still coerces to a 32-bit integer, not to the float that comes first.
+  // The coercion type is unsigned where take_int_float's is signed.  On a tie
+  // for widest member the classifier's reduction keeps the first field (float
+  // here), and resolving an integer coercion from a float storage type falls
+  // through to a byte-size fallback that is always unsigned.  That difference
+  // is confined to CIR: LLVM integers have no signedness, so both lower to
+  // i32, which the LLVM checks at the end of this file pin.
+  cir.func @take_float_int(%arg0: !rec_UFloatInt) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_float_int(%arg0: !u32i)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!u32i> -> !cir.ptr<!rec_UFloatInt>
+
+  // An 8-byte union fills its eightbyte and coerces to i64.
+  cir.func @take_long_double(%arg0: !rec_ULongDouble) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_long_double(%arg0: !s64i)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" align(8) : !cir.ptr<!s64i>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!s64i> -> !cir.ptr<!rec_ULongDouble>
+
+  // Same class-merge point at 8 bytes: double-first still coerces to a
+  // 64-bit integer, unsigned for the same reduction-tie reason as
+  // take_float_int above.
+  cir.func @take_double_long(%arg0: !rec_UDoubleLong) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_double_long(%arg0: !u64i)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!u64i> -> !cir.ptr<!rec_UDoubleLong>
+
+  // A union of floats classifies SSE, so the coercion is a float register
+  // rather than an integer one.
+  cir.func @take_floats(%arg0: !rec_UFloats) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_floats(%arg0: !cir.float)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" align(4) : !cir.ptr<!cir.float>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!cir.float> -> !cir.ptr<!rec_UFloats>
+
+  // A 3-byte union coerces to the i24 that spans it.
+  cir.func @take_three(%arg0: !rec_UThree) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_three(%arg0: !cir.int<u, 24>)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!cir.int<u, 24>> -> !cir.ptr<!rec_UThree>
+
+  // The highest-aligned member (the int) is narrower than the union, whose
+  // 8-byte size comes from the char array.  The eightbyte is sized from the
+  // union, not from that member, so this coerces to i64 rather than i32.
+  cir.func @take_narrow_storage(%arg0: !rec_UNarrowStorage) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_narrow_storage(%arg0: !u64i)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!u64i> -> !cir.ptr<!rec_UNarrowStorage>
+
+  // A 16-byte union is two INTEGER eightbytes, flattened into one argument per
+  // eightbyte.
+  cir.func @take_two_eightbytes(%arg0: !rec_UTwoEightbytes) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_two_eightbytes(%arg0: !s64i, %arg1: !u64i)
+  // CHECK:   cir.alloca "coerce" align(8) : !cir.ptr<!rec_anon_struct>
+  // CHECK:   %[[FLAT:.*]] = cir.alloca "coerce" align(8) : !cir.ptr<!rec_anon_struct>
+  // CHECK:   %[[E0:.*]] = cir.get_member %[[FLAT]][0] {{.*}} : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!s64i>
+  // CHECK:   cir.store %arg0, %[[E0]] : !s64i, !cir.ptr<!s64i>
+  // CHECK:   %[[E1:.*]] = cir.get_member %[[FLAT]][1] {{.*}} : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i>
+  // CHECK:   cir.store %arg1, %[[E1]] : !u64i, !cir.ptr<!u64i>
+  // CHECK:   %{{.*}} = cir.cast bitcast %{{.*}} : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!rec_UTwoEightbytes>
+
+  // A union too large for registers is passed byval.
+  cir.func @take_big(%arg0: !rec_UBig) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_big(%arg0: !cir.ptr<!rec_UBig> {llvm.align = 8 : i64, llvm.byval = !rec_UBig, llvm.noalias, llvm.noundef})
+  // CHECK:   %{{.*}} = cir.load %arg0 : !cir.ptr<!rec_UBig>, !rec_UBig
+
+  // The byval alignment comes from the record's declared alignment, which the
+  // layout metadata carries because the members alone cannot express an
+  // alignment attribute.  Same members as take_big, alignment 32 rather than 8.
+  cir.func @take_big_over_aligned(%arg0: !rec_UBigOverAligned) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_big_over_aligned(%arg0: !cir.ptr<!rec_UBigOverAligned> {llvm.align = 32 : i64, llvm.byval = !rec_UBigOverAligned, llvm.noalias, llvm.noundef})
+
+  // The same declared-alignment source feeds every accepted record, not just
+  // unions: an over-aligned STRUCT gets the same byval alignment fix, since
+  // mapCIRType's alignment lookup is on the shared record path.
+  cir.func @take_struct_over_aligned(%arg0: !rec_SOverAligned) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_struct_over_aligned(%arg0: !cir.ptr<!rec_SOverAligned> {llvm.align = 32 : i64, llvm.byval = !rec_SOverAligned, llvm.noalias, llvm.noundef})
+
+  // A union with no members classifies Ignore and is dropped from the
+  // signature.
+  cir.func @take_empty(%arg0: !rec_UEmpty) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty()
+
+  // An empty-union return is dropped too: the function returns void while its
+  // local storage slot survives.
+  cir.func @ret_empty() -> !rec_UEmpty {
+    %0 = cir.alloca "u" align(1) : !cir.ptr<!rec_UEmpty>
+    %1 = cir.load %0 : !cir.ptr<!rec_UEmpty>, !rec_UEmpty
+    cir.return %1 : !rec_UEmpty
+  }
+
+  // CHECK: cir.func{{.*}} @ret_empty()
+  // CHECK:   cir.alloca "u" align(1) : !cir.ptr<!rec_UEmpty>
+  // CHECK:   cir.return{{$}}
+
+  // A union the record layout marks as unable to pass in registers goes
+  // indirect without byval, however small it is.
+  cir.func @take_no_regs(%arg0: !rec_UNoRegs) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_no_regs(%arg0: !cir.ptr<!rec_UNoRegs> {llvm.align = 4 : i64, llvm.byref = !rec_UNoRegs})
+
+  // A struct member that is itself a union is mapped through the same union
+  // handling, so the enclosing 8-byte struct coerces to one i64.
+  cir.func @take_struct_with_union(%arg0: !rec_SWithUnion) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_struct_with_union(%arg0: !u64i)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!u64i> -> !cir.ptr<!rec_SWithUnion>
+
+  // A coerced union return round-trips through the coercion type.
+  cir.func @ret_int_float(%arg0: !rec_UIntFloat) -> !rec_UIntFloat {
+    %0 = cir.alloca "u" align(4) : !cir.ptr<!rec_UIntFloat>
+    cir.store %arg0, %0 : !rec_UIntFloat, !cir.ptr<!rec_UIntFloat>
+    %1 = cir.load %0 : !cir.ptr<!rec_UIntFloat>, !rec_UIntFloat
+    cir.return %1 : !rec_UIntFloat
+  }
+
+  // CHECK: cir.func{{.*}} @ret_int_float(%arg0: !s32i) -> !s32i
+  // CHECK:   %[[RETSLOT:.*]] = cir.alloca "coerce" align(4) : !cir.ptr<!rec_UIntFloat>
+  // CHECK:   cir.store %{{.*}}, %[[RETSLOT]] : !rec_UIntFloat, !cir.ptr<!rec_UIntFloat>
+  // CHECK:   %[[RETCAST:.*]] = cir.cast bitcast %[[RETSLOT]] : !cir.ptr<!rec_UIntFloat> -> !cir.ptr<!s32i>
+  // CHECK:   %[[RET:.*]] = cir.load %[[RETCAST]] : !cir.ptr<!s32i>, !s32i
+  // CHECK:   cir.return %[[RET]] : !s32i
+
+  // A union return too large for registers uses the caller's sret slot.
+  cir.func @ret_big(%arg0: !rec_UBig) -> !rec_UBig {
+    %0 = cir.alloca "u" align(1) : !cir.ptr<!rec_UBig>
+    cir.store %arg0, %0 : !rec_UBig, !cir.ptr<!rec_UBig>
+    %1 = cir.load %0 : !cir.ptr<!rec_UBig>, !rec_UBig
+    cir.return %1 : !rec_UBig
+  }
+
+  // CHECK: cir.func{{.*}} @ret_big(%arg0: !cir.ptr<!rec_UBig> {llvm.align = 1 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_UBig, llvm.writable}, %arg1: !cir.ptr<!rec_UBig> {llvm.align = 8 : i64, llvm.byval = !rec_UBig, llvm.noalias, llvm.noundef})
+  // CHECK:   %[[VAL:.*]] = cir.load %arg1 : !cir.ptr<!rec_UBig>, !rec_UBig
+  // CHECK:   cir.store %[[VAL]], %arg0 : !rec_UBig, !cir.ptr<!rec_UBig>
+
+  // The call site coerces the union argument the same way the callee expects
+  // it.
+  cir.func @call_int_float(%arg0: !rec_UIntFloat) {
+    cir.call @take_int_float(%arg0) : (!rec_UIntFloat) -> ()
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @call_int_float(%arg0: !s32i)
+  // CHECK:   %[[ARGSLOT:.*]] = cir.alloca "coerce" align(4) : !cir.ptr<!rec_UIntFloat>
+  // CHECK:   cir.store %{{.*}}, %[[ARGSLOT]] : !rec_UIntFloat, !cir.ptr<!rec_UIntFloat>
+  // CHECK:   %[[ARGCAST:.*]] = cir.cast bitcast %[[ARGSLOT]] : !cir.ptr<!rec_UIntFloat> -> !cir.ptr<!s32i>
+  // CHECK:   %[[ARG:.*]] = cir.load %[[ARGCAST]] : !cir.ptr<!s32i>, !s32i
+  // CHECK:   cir.call @take_int_float(%[[ARG]]) : (!s32i) -> ()
+
+  // Both eightbytes are decomposed at the call site.
+  cir.func @call_two_eightbytes(%arg0: !rec_UTwoEightbytes) {
+    cir.call @take_two_eightbytes(%arg0) : (!rec_UTwoEightbytes) -> ()
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @call_two_eightbytes(%arg0: !s64i, %arg1: !u64i)
+  // CHECK:   %[[CALLSLOT:.*]] = cir.alloca "coerce" align(8) : !cir.ptr<!rec_UTwoEightbytes>
+  // CHECK:   %[[CALLCAST:.*]] = cir.cast bitcast %[[CALLSLOT]] : !cir.ptr<!rec_UTwoEightbytes> -> !cir.ptr<!rec_anon_struct>
+  // CHECK:   %[[G0:.*]] = cir.get_member %[[CALLCAST]][0] {{.*}} : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!s64i>
+  // CHECK:   %[[A0:.*]] = cir.load %[[G0]] : !cir.ptr<!s64i>, !s64i
+  // CHECK:   %[[G1:.*]] = cir.get_member %[[CALLCAST]][1] {{.*}} : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i>
+  // CHECK:   %[[A1:.*]] = cir.load %[[G1]] : !cir.ptr<!u64i>, !u64i
+  // CHECK:   cir.call @take_two_eightbytes(%[[A0]], %[[A1]]) : (!s64i, !u64i) -> ()
+}
+
+// LLVM: define void @take_int_float(i32 %{{.+}})
+// Declaration order does not reach the lowered signature: the CIR-level
+// signedness difference from the reduction tie disappears here, and both
+// orders land on the same integer register the class merge picked.
+// LLVM: define void @take_float_int(i32 %{{.+}})
+// LLVM: define void @take_long_double(i64 %{{.+}})
+// LLVM: define void @take_double_long(i64 %{{.+}})
+// LLVM: define void @take_floats(float %{{.+}})
+// LLVM: define void @take_three(i24 %{{.+}})
+// LLVM: define void @take_narrow_storage(i64 %{{.+}})
+// LLVM: define void @take_two_eightbytes(i64 %{{.+}}, i64 %{{.+}})
+// LLVM: define void @take_big(ptr noalias noundef byval(%union.UBig) align 8 %{{.+}})
+// LLVM: define void @take_big_over_aligned(ptr noalias noundef byval(%union.UBigOverAligned) align 32 %{{.+}})
+// LLVM: define void @take_struct_over_aligned(ptr noalias noundef byval(%struct.SOverAligned) align 32 %{{.+}})
+// LLVM: define void @take_empty()
+// LLVM: define void @ret_empty()
+// LLVM: define void @take_no_regs(ptr byref(%union.UNoRegs) align 4 %{{.+}})
+// LLVM: define void @take_struct_with_union(i64 %{{.+}})
+// LLVM: define i32 @ret_int_float(i32 %{{.+}})
+// LLVM: define void @ret_big(ptr dead_on_unwind noalias writable sret(%union.UBig) align 1 %{{.+}}, ptr noalias noundef byval(%union.UBig) align 8 %{{.+}})
+// LLVM: define void @call_int_float(i32 %{{.+}})
+// LLVM:   call void @take_int_float(i32 %{{.+}})
+// LLVM: define void @call_two_eightbytes(i64 %{{.+}}, i64 %{{.+}})
+// LLVM:   call void @take_two_eightbytes(i64 %{{.+}}, i64 %{{.+}})

>From 8a1d0f3c31b81a9f4af34fff8c0dc70a059fd1f5 Mon Sep 17 00:00:00 2001
From: Walter Lee <49250218+googlewalt at users.noreply.github.com>
Date: Wed, 5 Aug 2026 18:56:18 -0400
Subject: [PATCH 16/18] [llvm] Add missing include (#214349)

Fix ada3786e91ca2058f3ac8255a024c12ae7d263ee.
---
 llvm/lib/Support/BalancedPartitioning.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/lib/Support/BalancedPartitioning.cpp b/llvm/lib/Support/BalancedPartitioning.cpp
index 2ae20e96845a2..56b4d0e604d24 100644
--- a/llvm/lib/Support/BalancedPartitioning.cpp
+++ b/llvm/lib/Support/BalancedPartitioning.cpp
@@ -18,6 +18,8 @@
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/ThreadPool.h"
 
+#include <cmath>
+
 using namespace llvm;
 #define DEBUG_TYPE "balanced-partitioning"
 

>From 2967a6c9718263a3cc332ccb4d54bd03cf1dc7e4 Mon Sep 17 00:00:00 2001
From: Reid Kleckner <rkleckner at nvidia.com>
Date: Wed, 5 Aug 2026 15:56:31 -0700
Subject: [PATCH 17/18] [cmake][docs] Fix incremental docs builds under
 deletion and renames (#211411)

Currently clang, flang, and libc all copy documentation from the source
tree into the build tree in preparation to build it, usually to create a
combined tree that includes generated documentation files, like
AttributeReference.md. However, renaming a document and rebuilding
without cleaning the docs tree leaves behind stale documentation files
that can accumulate.

This patch fixes the problem with two build actions:

1. List source documentation files. Always out of date, always
regenerates on every doc build, but it's very cheap.
2. Copy all source documents to the output, and delete any file that
neither originates from the source directory nor is mentioned as a
generated source to preserve.

These actions are implemented as CMake script files (`cmake -P`) since
they do things not covered by the builtin tools (`cmake -E
copy_if_different`). They could be simplified if we were willing to
tolerate more process launch overhead, but for something that runs on
the critical path to every doc rebuild, I decided it was worth spending
lines of CMake script on it.

Assisted-by: a coding tool
---
 clang/docs/CMakeLists.txt                  |  19 ++--
 flang/docs/CMakeLists.txt                  |  26 +++--
 libc/docs/CMakeLists.txt                   |  67 ++++++-------
 llvm/cmake/modules/AddSphinxTarget.cmake   |  96 +++++++++++++++++++
 llvm/cmake/modules/SphinxSourceScan.cmake  |  85 +++++++++++++++++
 llvm/cmake/modules/SphinxSourceSync.cmake  | 105 +++++++++++++++++++++
 llvm/cmake/modules/SphinxSourceUtils.cmake |  29 ++++++
 7 files changed, 379 insertions(+), 48 deletions(-)
 create mode 100644 llvm/cmake/modules/SphinxSourceScan.cmake
 create mode 100644 llvm/cmake/modules/SphinxSourceSync.cmake
 create mode 100644 llvm/cmake/modules/SphinxSourceUtils.cmake

diff --git a/clang/docs/CMakeLists.txt b/clang/docs/CMakeLists.txt
index 10992eea61c28..f4e9547ad37ce 100644
--- a/clang/docs/CMakeLists.txt
+++ b/clang/docs/CMakeLists.txt
@@ -87,6 +87,7 @@ function (gen_rst_file_from_td output_file td_option source docs_targets)
   get_filename_component(TABLEGEN_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${source}" DIRECTORY)
   list(APPEND LLVM_TABLEGEN_FLAGS "-I${TABLEGEN_INCLUDE_DIR}")
   clang_tablegen(${output_file} ${td_option} SOURCE ${source} TARGET "gen-${output_file}")
+  add_dependencies("gen-${output_file}" copy-clang-rst-docs)
   foreach(target ${docs_targets})
     add_dependencies(${target} gen-${output_file})
   endforeach()
@@ -95,16 +96,22 @@ endfunction()
 if (LLVM_ENABLE_SPHINX)
   include(AddSphinxTarget)
   if (SPHINX_FOUND AND (${SPHINX_OUTPUT_HTML} OR ${SPHINX_OUTPUT_MAN}))
-    # Copy rst files to build directory before generating the html
-    # documentation.  Some of the rst files are generated, so they
+    # Copy documentation sources to the build directory before generating
+    # documentation.  Some of the files are generated, so they
     # only exist in the build directory.  Sphinx needs all files in
     # the same directory in order to generate the html, so we need to
-    # copy all the non-gnerated rst files from the source to the build
+    # copy all the non-generated source files from the source to the build
     # directory before we run sphinx.
-    add_custom_target(copy-clang-rst-docs
-      COMMAND "${CMAKE_COMMAND}" -E copy_directory
+    set(clang_generated_docs
+      AttributeReference.rst
+      DiagnosticsReference.rst
+      AMDGPUBuiltinReference.rst
+      ClangCommandLineReference.rst
+      analyzer/user-docs/Options.rst)
+    add_sphinx_source_sync_target(copy-clang-rst-docs
       "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}"
-
+      PRESERVE_DOCS ${clang_generated_docs})
+    add_custom_command(TARGET copy-clang-rst-docs POST_BUILD
       COMMAND "${CMAKE_COMMAND}" -E copy_if_different
       "${CMAKE_CURRENT_SOURCE_DIR}/../Maintainers.md"
       "${CMAKE_CURRENT_BINARY_DIR}"
diff --git a/flang/docs/CMakeLists.txt b/flang/docs/CMakeLists.txt
index ddf3fb4543b6f..89fd219ef88cf 100644
--- a/flang/docs/CMakeLists.txt
+++ b/flang/docs/CMakeLists.txt
@@ -115,16 +115,22 @@ if (LLVM_ENABLE_SPHINX)
       # Copy the entire flang/docs directory to the build Source dir,
       # then remove the CommandGuide index.md file, to avoid clash with index.md
       # which is used for the HTML build.
-      add_custom_target(copy-flang-src-docs-html
-        COMMAND "${CMAKE_COMMAND}" -E copy_directory
-              "${CMAKE_CURRENT_SOURCE_DIR}"
-              "${FLANG_DOCS_HTML_DIR}"
+      add_sphinx_source_sync_target(copy-flang-src-docs-html
+        "${CMAKE_CURRENT_SOURCE_DIR}"
+        "${FLANG_DOCS_HTML_DIR}"
+        PRESERVE_DOCS
+          FlangCommandLineReference.rst
+          FIRLangRef.md
+        IGNORE_MISSING_FILES
+          CommandGuide/index.md)
+      add_custom_command(TARGET copy-flang-src-docs-html POST_BUILD
         COMMAND "${CMAKE_COMMAND}" -E remove
           "${FLANG_DOCS_HTML_DIR}/CommandGuide/index.md"
-        COMMAND "${CMAKE_COMMAND}" -E copy
+        COMMAND "${CMAKE_COMMAND}" -E copy_if_different
           "${CMAKE_CURRENT_BINARY_DIR}/Source/FlangCommandLineReference.rst"
-          "${FLANG_DOCS_HTML_DIR}/FlangCommandLineReference.rst"
-        DEPENDS flang-doc gen-FlangCommandLineReference.rst)
+          "${FLANG_DOCS_HTML_DIR}/FlangCommandLineReference.rst")
+      add_dependencies(copy-flang-src-docs-html
+        flang-doc gen-FlangCommandLineReference.rst)
 
       # ${CMAKE_CURRENT_BINARY_DIR}/Dialect/FIRLangRef.md is generated by
       # mlir-tblgen. The script executed in the command below adds some text to
@@ -154,13 +160,13 @@ if (LLVM_ENABLE_SPHINX)
       add_custom_target(copy-flang-src-docs-man
         COMMAND "${CMAKE_COMMAND}" -E make_directory
                 "${FLANG_DOCS_MAN_DIR}"
-        COMMAND "${CMAKE_COMMAND}" -E copy
+        COMMAND "${CMAKE_COMMAND}" -E copy_if_different
           "${CMAKE_CURRENT_SOURCE_DIR}/conf.py"
           "${FLANG_DOCS_MAN_DIR}/conf.py"
-        COMMAND "${CMAKE_COMMAND}" -E copy
+        COMMAND "${CMAKE_COMMAND}" -E copy_if_different
           "${CMAKE_CURRENT_BINARY_DIR}/Source/FlangCommandLineOptions.rst"
           "${FLANG_DOCS_MAN_DIR}/FlangCommandLineOptions.rst"
-        COMMAND "${CMAKE_COMMAND}" -E copy
+        COMMAND "${CMAKE_COMMAND}" -E copy_if_different
                 "${CMAKE_CURRENT_SOURCE_DIR}/CommandGuide/index.md"
                 "${FLANG_DOCS_MAN_DIR}/index.md"
         DEPENDS flang-doc gen-FlangCommandLineOptions.rst)
diff --git a/libc/docs/CMakeLists.txt b/libc/docs/CMakeLists.txt
index 261addc0006ff..08a56dfc9a819 100644
--- a/libc/docs/CMakeLists.txt
+++ b/libc/docs/CMakeLists.txt
@@ -2,38 +2,10 @@ if (LLVM_ENABLE_SPHINX)
 include(AddSphinxTarget)
 if (SPHINX_FOUND)
   if (${SPHINX_OUTPUT_HTML})
-    # Similar to clang, we copy our static .rst files from libc/docs/ to the
-    # $build_dir/libc/docs/. That way, we can have a mix of both static
-    # (committed) .rst files, and dynamically generated .rst files. We don't
-    # want the dynamically generated .rst files to pollute the source tree.
-    add_custom_target(copy-libc-rst-docs
-      COMMAND "${CMAKE_COMMAND}" -E copy_directory
-              "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}"
-
-      COMMAND "${CMAKE_COMMAND}" -E copy_if_different
-              "${CMAKE_CURRENT_SOURCE_DIR}/../Maintainers.md"
-              "${CMAKE_CURRENT_BINARY_DIR}"
-      )
-
-    # For headers that are nested in directories, we need to
-    # `mkdir $build_dir/libc/docs/headers/$dir` since the above copy_directory
-    # command does not create such copies. Otherwise, the invocation of docgen
-    # below will fail since the output file would be placed in a directory that
-    # does not exist, leading to a `No such file or directory` error from the
-    # shell.
-    file(MAKE_DIRECTORY
-      "${CMAKE_CURRENT_BINARY_DIR}/headers/arpa/"
-      "${CMAKE_CURRENT_BINARY_DIR}/headers/net/"
-      "${CMAKE_CURRENT_BINARY_DIR}/headers/netinet/"
-      "${CMAKE_CURRENT_BINARY_DIR}/headers/sys/"
-    )
-
-    # Change sphinx to build from $build_dir/libc/docs/ rather than
-    # llvm-project/libc/docs/.
-    add_sphinx_target(html libc SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
-    # Depend on the copy target.
-    add_dependencies(docs-libc-html copy-libc-rst-docs)
-
+    # Similar to clang, we copy our static documentation files from libc/docs/
+    # to the $build_dir/libc/docs/. That way, we can have a mix of both static
+    # committed files and dynamically generated .rst files. We don't want the
+    # dynamically generated .rst files to pollute the source tree.
     # Maintain a list of headers for which we dynamically generate html docs
     # for via docgen. For more complex docs (such as per arch support, a la
     # math.h), those should be omitted and exist statically in
@@ -92,6 +64,36 @@ if (SPHINX_FOUND)
       wchar
       wctype
     )
+    foreach(stem IN LISTS docgen_list)
+      list(APPEND libc_generated_docs "headers/${stem}.rst")
+    endforeach()
+    list(APPEND libc_generated_docs configure.rst)
+    add_sphinx_source_sync_target(copy-libc-rst-docs
+      "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}"
+      PRESERVE_DOCS ${libc_generated_docs})
+    add_custom_command(TARGET copy-libc-rst-docs POST_BUILD
+      COMMAND "${CMAKE_COMMAND}" -E copy_if_different
+              "${CMAKE_CURRENT_SOURCE_DIR}/../Maintainers.md"
+              "${CMAKE_CURRENT_BINARY_DIR}"
+      )
+
+    # For headers that are nested in directories, we need to
+    # `mkdir $build_dir/libc/docs/headers/$dir`. Otherwise, the invocation of
+    # docgen below will fail since the output file would be placed in a
+    # directory that does not exist, leading to a `No such file or directory`
+    # error from the shell.
+    file(MAKE_DIRECTORY
+      "${CMAKE_CURRENT_BINARY_DIR}/headers/arpa/"
+      "${CMAKE_CURRENT_BINARY_DIR}/headers/net/"
+      "${CMAKE_CURRENT_BINARY_DIR}/headers/netinet/"
+      "${CMAKE_CURRENT_BINARY_DIR}/headers/sys/"
+    )
+
+    # Change sphinx to build from $build_dir/libc/docs/ rather than
+    # llvm-project/libc/docs/.
+    add_sphinx_target(html libc SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+    # Depend on the copy target.
+    add_dependencies(docs-libc-html copy-libc-rst-docs)
 
     foreach(stem IN LISTS docgen_list)
       # It is an error in cmake to have a target name that contains a "/", but
@@ -105,6 +107,7 @@ if (SPHINX_FOUND)
       add_custom_target(${docgen_target_name}
         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../utils/docgen/docgen.py ${stem}.h >
                 ${CMAKE_CURRENT_BINARY_DIR}/headers/${stem}.rst)
+      add_dependencies(${docgen_target_name} copy-libc-rst-docs)
       # depend on the docgen invocation.
       add_dependencies(docs-libc-html ${docgen_target_name})
     endforeach()
diff --git a/llvm/cmake/modules/AddSphinxTarget.cmake b/llvm/cmake/modules/AddSphinxTarget.cmake
index 379e36db48a25..02a869b7941a9 100644
--- a/llvm/cmake/modules/AddSphinxTarget.cmake
+++ b/llvm/cmake/modules/AddSphinxTarget.cmake
@@ -12,6 +12,102 @@ else()
   message(STATUS "Sphinx disabled.")
 endif()
 
+# Create a target that synchronizes checked-in Sphinx inputs into a build-tree
+# source directory. The synchronization preserves mtimes for unchanged files,
+# updates changed files, and removes stale .rst/.md files so incremental docs
+# builds stay correct when documentation is renamed or deleted. The cheap scan
+# target is always run to notice directory listing changes, but it writes the
+# file list only when it changes; the sync command depends on that list and on a
+# depfile of the listed source files. The scan also records the destination
+# .rst/.md listing so destination-only stale files trigger cleanup. It also
+# records source files that are missing from the destination so externally
+# removed copies are restored. The actual copy step can still be skipped when
+# both trees are unchanged. Use PRESERVE_DOCS for generated .rst/.md files that
+# live in the destination directory but do not exist in source_dir. Use
+# IGNORE_MISSING_FILES for files that are copied by the sync step but
+# intentionally removed by a later build action.
+function(add_sphinx_source_sync_target target source_dir destination_dir)
+  cmake_parse_arguments(ARG "" "" "PRESERVE_DOCS;IGNORE_MISSING_FILES" ${ARGN})
+
+  set(sync_dir "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${target}.sphinx-source-sync")
+  set(file_list "${sync_dir}/files.txt")
+  set(destination_doc_list "${sync_dir}/destination-docs.txt")
+  set(missing_file_list "${sync_dir}/missing-files.txt")
+  set(preserve_file "${sync_dir}/preserve.txt")
+  set(ignore_missing_file "${sync_dir}/ignore-missing.txt")
+  set(manifest_file "${sync_dir}/manifest.txt")
+  set(depfile "${sync_dir}/sync.d")
+  set(stamp_file "${sync_dir}/sync.stamp")
+  set(scan_target "${target}-scan")
+  set(scan_script "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SphinxSourceScan.cmake")
+  set(sync_script "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SphinxSourceSync.cmake")
+  set(utils_script "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SphinxSourceUtils.cmake")
+
+  file(MAKE_DIRECTORY "${sync_dir}")
+  string(REPLACE ";" "\n" preserve_docs "${ARG_PRESERVE_DOCS}")
+  if (preserve_docs)
+    string(APPEND preserve_docs "\n")
+  endif()
+  if (EXISTS "${preserve_file}")
+    file(READ "${preserve_file}" old_preserve_docs)
+  else()
+    set(old_preserve_docs)
+  endif()
+  if (NOT preserve_docs STREQUAL old_preserve_docs)
+    file(WRITE "${preserve_file}" "${preserve_docs}")
+  endif()
+  string(REPLACE ";" "\n" ignore_missing_files "${ARG_IGNORE_MISSING_FILES}")
+  if (ignore_missing_files)
+    string(APPEND ignore_missing_files "\n")
+  endif()
+  if (EXISTS "${ignore_missing_file}")
+    file(READ "${ignore_missing_file}" old_ignore_missing_files)
+  else()
+    set(old_ignore_missing_files)
+  endif()
+  if (NOT ignore_missing_files STREQUAL old_ignore_missing_files)
+    file(WRITE "${ignore_missing_file}" "${ignore_missing_files}")
+  endif()
+
+  add_custom_target(${scan_target}
+                    COMMAND "${CMAKE_COMMAND}"
+                            "-DSOURCE_DIR=${source_dir}"
+                            "-DDESTINATION_DIR=${destination_dir}"
+                            "-DFILE_LIST=${file_list}"
+                            "-DDESTINATION_DOC_LIST=${destination_doc_list}"
+                            "-DMISSING_FILE_LIST=${missing_file_list}"
+                            "-DIGNORE_MISSING_FILE=${ignore_missing_file}"
+                            -P "${scan_script}"
+                    BYPRODUCTS "${file_list}" "${destination_doc_list}"
+                               "${missing_file_list}"
+                    DEPENDS "${scan_script}" "${utils_script}"
+                    COMMENT
+                    "Scanning Sphinx sources in \"${source_dir}\""
+                    VERBATIM)
+
+  add_custom_command(OUTPUT "${stamp_file}"
+                     COMMAND "${CMAKE_COMMAND}"
+                             "-DSOURCE_DIR=${source_dir}"
+                             "-DDESTINATION_DIR=${destination_dir}"
+                             "-DFILE_LIST=${file_list}"
+                             "-DPRESERVE_FILE=${preserve_file}"
+                             "-DMANIFEST_FILE=${manifest_file}"
+                             "-DDEPFILE=${depfile}"
+                             "-DSTAMP_FILE=${stamp_file}"
+                             -P "${sync_script}"
+                     DEPENDS "${file_list}" "${preserve_file}"
+                             "${destination_doc_list}" "${missing_file_list}"
+                             "${ignore_missing_file}" "${sync_script}"
+                             "${utils_script}"
+                     DEPFILE "${depfile}"
+                     BYPRODUCTS "${manifest_file}"
+                     COMMENT
+                     "Copying Sphinx sources from \"${source_dir}\" to \"${destination_dir}\""
+                     VERBATIM)
+
+  add_custom_target(${target} DEPENDS "${stamp_file}")
+  add_dependencies(${target} ${scan_target})
+endfunction()
 
 # Handy function for creating the different Sphinx targets.
 #
diff --git a/llvm/cmake/modules/SphinxSourceScan.cmake b/llvm/cmake/modules/SphinxSourceScan.cmake
new file mode 100644
index 0000000000000..b6dd6da08aa37
--- /dev/null
+++ b/llvm/cmake/modules/SphinxSourceScan.cmake
@@ -0,0 +1,85 @@
+# Enumerate a documentation source tree for SphinxSourceSync.cmake.
+#
+# Usage:
+#
+#   cmake -DSOURCE_DIR=/path/to/project/docs \
+#         -DDESTINATION_DIR=/path/to/build/project/docs \
+#         -DFILE_LIST=/path/to/build/file-list.txt \
+#         -DDESTINATION_DOC_LIST=/path/to/build/destination-docs.txt \
+#         -DMISSING_FILE_LIST=/path/to/build/missing-files.txt \
+#         -DIGNORE_MISSING_FILE=/path/to/build/ignore-missing.txt \
+#         -P /path/to/SphinxSourceScan.cmake
+#
+# The output file contains source-relative paths, one per line. It is written
+# only when the directory listing changes, allowing downstream custom commands
+# to skip work when the always-run scan observes no additions, removals, or
+# renames. DESTINATION_DOC_LIST similarly records destination-relative .rst and
+# .md files so destination-only stale docs can trigger cleanup.
+# MISSING_FILE_LIST records source files that do not currently exist in the
+# destination tree, excluding paths listed in IGNORE_MISSING_FILE.
+
+include("${CMAKE_CURRENT_LIST_DIR}/SphinxSourceUtils.cmake")
+
+if (NOT DEFINED SOURCE_DIR)
+  message(FATAL_ERROR "SOURCE_DIR must be set")
+endif()
+
+if (NOT DEFINED FILE_LIST)
+  message(FATAL_ERROR "FILE_LIST must be set")
+endif()
+
+if (NOT DEFINED DESTINATION_DIR)
+  message(FATAL_ERROR "DESTINATION_DIR must be set")
+endif()
+
+if (NOT DEFINED DESTINATION_DOC_LIST)
+  message(FATAL_ERROR "DESTINATION_DOC_LIST must be set")
+endif()
+
+if (NOT DEFINED MISSING_FILE_LIST)
+  message(FATAL_ERROR "MISSING_FILE_LIST must be set")
+endif()
+
+if (NOT DEFINED IGNORE_MISSING_FILE)
+  message(FATAL_ERROR "IGNORE_MISSING_FILE must be set")
+endif()
+
+file(GLOB_RECURSE source_files
+  LIST_DIRECTORIES false
+  RELATIVE "${SOURCE_DIR}"
+  "${SOURCE_DIR}/*")
+list(SORT source_files)
+
+set(contents)
+foreach(relative_path IN LISTS source_files)
+  string(APPEND contents "${relative_path}\n")
+endforeach()
+write_if_changed("${FILE_LIST}" "${contents}")
+
+file(STRINGS "${IGNORE_MISSING_FILE}" ignore_missing_files)
+set(contents)
+foreach(relative_path IN LISTS source_files)
+  list(FIND ignore_missing_files "${relative_path}" ignore_index)
+  if (ignore_index EQUAL -1 AND
+      NOT EXISTS "${DESTINATION_DIR}/${relative_path}")
+    string(APPEND contents "${relative_path}\n")
+  endif()
+endforeach()
+write_if_changed("${MISSING_FILE_LIST}" "${contents}")
+
+if (EXISTS "${DESTINATION_DIR}")
+  file(GLOB_RECURSE destination_docs
+    LIST_DIRECTORIES false
+    RELATIVE "${DESTINATION_DIR}"
+    "${DESTINATION_DIR}/*.rst"
+    "${DESTINATION_DIR}/*.md")
+  list(SORT destination_docs)
+else()
+  set(destination_docs)
+endif()
+
+set(contents)
+foreach(relative_path IN LISTS destination_docs)
+  string(APPEND contents "${relative_path}\n")
+endforeach()
+write_if_changed("${DESTINATION_DOC_LIST}" "${contents}")
diff --git a/llvm/cmake/modules/SphinxSourceSync.cmake b/llvm/cmake/modules/SphinxSourceSync.cmake
new file mode 100644
index 0000000000000..165ae718c69c1
--- /dev/null
+++ b/llvm/cmake/modules/SphinxSourceSync.cmake
@@ -0,0 +1,105 @@
+# Synchronize a documentation source tree into an intermediate Sphinx source
+# tree.
+#
+# Usage:
+#
+#   cmake -DSOURCE_DIR=/path/to/project/docs \
+#         -DDESTINATION_DIR=/path/to/build/project/docs \
+#         -DFILE_LIST=/path/to/build/file-list.txt \
+#         -DPRESERVE_FILE=/path/to/build/preserve.txt \
+#         -DMANIFEST_FILE=/path/to/build/manifest.txt \
+#         -DDEPFILE=/path/to/build/sync.d \
+#         -DSTAMP_FILE=/path/to/build/sync.stamp \
+#         -P /path/to/SphinxSourceSync.cmake
+#
+# The sync copies all source files into DESTINATION_DIR, avoids rewriting files
+# whose contents are unchanged, and removes stale .rst and .md files that no
+# longer exist under SOURCE_DIR unless they are listed in PRESERVE_FILE. This
+# is intended for Sphinx builds that merge checked-in docs with generated docs
+# in a build-tree source directory.
+
+include("${CMAKE_CURRENT_LIST_DIR}/SphinxSourceUtils.cmake")
+
+if (NOT DEFINED SOURCE_DIR)
+  message(FATAL_ERROR "SOURCE_DIR must be set")
+endif()
+
+if (NOT DEFINED DESTINATION_DIR)
+  message(FATAL_ERROR "DESTINATION_DIR must be set")
+endif()
+
+if (NOT DEFINED FILE_LIST)
+  message(FATAL_ERROR "FILE_LIST must be set")
+endif()
+
+if (NOT DEFINED PRESERVE_FILE)
+  message(FATAL_ERROR "PRESERVE_FILE must be set")
+endif()
+
+if (NOT DEFINED MANIFEST_FILE)
+  message(FATAL_ERROR "MANIFEST_FILE must be set")
+endif()
+
+if (NOT DEFINED DEPFILE)
+  message(FATAL_ERROR "DEPFILE must be set")
+endif()
+
+if (NOT DEFINED STAMP_FILE)
+  message(FATAL_ERROR "STAMP_FILE must be set")
+endif()
+
+file(MAKE_DIRECTORY "${DESTINATION_DIR}")
+file(STRINGS "${FILE_LIST}" source_files)
+file(STRINGS "${PRESERVE_FILE}" preserve_docs)
+
+set(depfile_dependencies)
+set(source_docs)
+foreach(relative_path IN LISTS source_files)
+  set(source_path "${SOURCE_DIR}/${relative_path}")
+  set(destination_path "${DESTINATION_DIR}/${relative_path}")
+  get_filename_component(destination_parent "${destination_path}" DIRECTORY)
+  file(MAKE_DIRECTORY "${destination_parent}")
+  # configure_file(COPYONLY) preserves mtimes for unchanged files and avoids
+  # the subprocess overhead of cmake -E copy_if_different.
+  configure_file("${source_path}" "${destination_path}" COPYONLY)
+  list(APPEND depfile_dependencies "${source_path}")
+
+  string(TOLOWER "${relative_path}" lower_relative_path)
+  if (lower_relative_path MATCHES "\\.(rst|md)$")
+    list(APPEND source_docs "${relative_path}")
+  endif()
+endforeach()
+
+file(GLOB_RECURSE destination_docs
+  LIST_DIRECTORIES false
+  RELATIVE "${DESTINATION_DIR}"
+  "${DESTINATION_DIR}/*.rst"
+  "${DESTINATION_DIR}/*.md")
+foreach(relative_path IN LISTS destination_docs)
+  list(FIND source_docs "${relative_path}" source_index)
+  list(FIND preserve_docs "${relative_path}" preserve_index)
+  # Generated docs are absent from SOURCE_DIR, but callers list them in
+  # PRESERVE_FILE so stale-source cleanup does not remove build outputs.
+  if (source_index EQUAL -1 AND preserve_index EQUAL -1)
+    file(REMOVE "${DESTINATION_DIR}/${relative_path}")
+  endif()
+endforeach()
+
+set(manifest_contents)
+foreach(relative_path IN LISTS source_docs)
+  string(APPEND manifest_contents "${relative_path}\n")
+endforeach()
+write_if_changed("${MANIFEST_FILE}" "${manifest_contents}")
+
+escape_depfile_path("${STAMP_FILE}" escaped_stamp)
+set(depfile_contents "${escaped_stamp}:")
+foreach(source_path IN LISTS depfile_dependencies)
+  escape_depfile_path("${source_path}" escaped_source_path)
+  string(APPEND depfile_contents " ${escaped_source_path}")
+endforeach()
+string(APPEND depfile_contents "\n")
+file(WRITE "${DEPFILE}" "${depfile_contents}")
+
+get_filename_component(stamp_dir "${STAMP_FILE}" DIRECTORY)
+file(MAKE_DIRECTORY "${stamp_dir}")
+file(TOUCH "${STAMP_FILE}")
diff --git a/llvm/cmake/modules/SphinxSourceUtils.cmake b/llvm/cmake/modules/SphinxSourceUtils.cmake
new file mode 100644
index 0000000000000..9618312d5cfb3
--- /dev/null
+++ b/llvm/cmake/modules/SphinxSourceUtils.cmake
@@ -0,0 +1,29 @@
+# Common helpers for Sphinx source scan and sync scripts.
+
+# CMake-script native implementation of cmake -E write_if_changed. This keeps
+# frequently run scan/sync scripts from paying for an extra subprocess just to
+# avoid rewriting unchanged files.
+function(write_if_changed output_file contents)
+  if (EXISTS "${output_file}")
+    file(READ "${output_file}" old_contents)
+  else()
+    set(old_contents)
+  endif()
+
+  if (NOT contents STREQUAL old_contents)
+    get_filename_component(output_dir "${output_file}" DIRECTORY)
+    file(MAKE_DIRECTORY "${output_dir}")
+    file(WRITE "${output_file}" "${contents}")
+  endif()
+endfunction()
+
+# Escape paths for Ninja/Make depfile syntax before writing them into a depfile
+# generated by a CMake script.
+function(escape_depfile_path input output)
+  set(path "${input}")
+  string(REPLACE "\\" "/" path "${path}")
+  string(REPLACE "$" "$$" path "${path}")
+  string(REPLACE "#" "\\#" path "${path}")
+  string(REPLACE " " "\\ " path "${path}")
+  set(${output} "${path}" PARENT_SCOPE)
+endfunction()

>From 7e95f37d5a9072677f6c9a78aa0df16cc4521c6c Mon Sep 17 00:00:00 2001
From: yahia ahmed <yahia.a.abdrabou at gmail.com>
Date: Mon, 24 Aug 2026 06:43:06 +0300
Subject: [PATCH 18/18] [libc][stdio] Add support for the %m modifier

---
 libc/src/stdio/scanf_core/CMakeLists.txt      |  1 +
 libc/src/stdio/scanf_core/parser.h            |  9 ++--
 libc/src/stdio/scanf_core/string_converter.h  | 52 +++++++++++++++++--
 libc/test/src/stdio/scanf_core/CMakeLists.txt |  1 +
 4 files changed, 53 insertions(+), 10 deletions(-)

diff --git a/libc/src/stdio/scanf_core/CMakeLists.txt b/libc/src/stdio/scanf_core/CMakeLists.txt
index 78d0354365102..48005531d933b 100644
--- a/libc/src/stdio/scanf_core/CMakeLists.txt
+++ b/libc/src/stdio/scanf_core/CMakeLists.txt
@@ -105,6 +105,7 @@ add_header_library(
     libc.src.__support.CPP.limits
     libc.src.__support.char_vector
     libc.src.__support.str_to_float
+    libc.src.__support.CPP.new
   ${use_system_file}
 )
 
diff --git a/libc/src/stdio/scanf_core/parser.h b/libc/src/stdio/scanf_core/parser.h
index 1e2f26e0d3fdd..37e49e6878796 100644
--- a/libc/src/stdio/scanf_core/parser.h
+++ b/libc/src/stdio/scanf_core/parser.h
@@ -81,11 +81,10 @@ template <typename ArgProvider> class Parser {
         cur_pos = cur_pos + static_cast<size_t>(result.parsed_len);
       }
 
-      // TODO(michaelrj): add posix allocate flag support.
-      // if (str[cur_pos] == 'm') {
-      //   ++cur_pos;
-      //   section.flags = FormatFlags::ALLOCATE;
-      // }
+      if (str[cur_pos] == 'm') {
+        ++cur_pos;
+        section.flags = FormatFlags::ALLOCATE;
+      }
 
       LengthModifier lm = parse_length_modifier(&cur_pos);
       section.length_modifier = lm;
diff --git a/libc/src/stdio/scanf_core/string_converter.h b/libc/src/stdio/scanf_core/string_converter.h
index 3879f8c995899..7986876a35dc6 100644
--- a/libc/src/stdio/scanf_core/string_converter.h
+++ b/libc/src/stdio/scanf_core/string_converter.h
@@ -10,10 +10,14 @@
 #define LLVM_LIBC_SRC_STDIO_SCANF_CORE_STRING_CONVERTER_H
 
 #include "src/__support/CPP/limits.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/alloc-checker.h"
 #include "src/__support/ctype_utils.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 #include "src/stdio/scanf_core/core_structs.h"
 #include "src/stdio/scanf_core/reader.h"
+#include "src/string/memory_utils/inline_memcpy.h"
 
 #include <stddef.h>
 
@@ -40,8 +44,21 @@ int convert_string(Reader<T> *reader, const FormatSection &to_conv) {
     }
   }
 
-  char *output = reinterpret_cast<char *>(to_conv.output_ptr);
-
+  char *output;
+  size_t value;
+  AllocChecker ac;
+  if ((to_conv.flags & NO_WRITE) == 0 && (to_conv.flags & ALLOCATE) != 0) {
+    if (to_conv.conv_name == 'c')
+      value = max_width + 1;
+    else
+      value = (max_width < 32) ? max_width + 1 : 32;
+    output = new (ac) char[value];
+    if (!ac) {
+      libc_errno = ENOMEM;
+      return MATCHING_FAILURE;
+    }
+  } else
+    output = reinterpret_cast<char *>(to_conv.output_ptr);
   char cur_char = reader->getc();
   size_t i = 0;
   for (; i < max_width && cur_char != '\0'; ++i) {
@@ -52,8 +69,24 @@ int convert_string(Reader<T> *reader, const FormatSection &to_conv) {
       break;
     }
     // if the NO_WRITE flag is not set, write to the output.
-    if ((to_conv.flags & NO_WRITE) == 0)
+    if ((to_conv.flags & NO_WRITE) == 0) {
       output[i] = cur_char;
+      if ((to_conv.flags & ALLOCATE) != 0) {
+        if ((i + 1) == value && value < max_width) {
+          value *= 2;
+          char *tmp = new (ac) char[value];
+          if (!ac) {
+            delete[] output;
+            libc_errno = ENOMEM;
+            reader->ungetc(cur_char);
+            return MATCHING_FAILURE;
+          }
+          inline_memcpy(tmp, output, i + 1);
+          delete[] output;
+          output = tmp;
+        }
+      }
+    }
     cur_char = reader->getc();
   }
 
@@ -61,6 +94,12 @@ int convert_string(Reader<T> *reader, const FormatSection &to_conv) {
   // last one back.
   reader->ungetc(cur_char);
 
+  if (i == 0) {
+    if ((to_conv.flags & ALLOCATE) != 0 && output)
+      delete[] output;
+    return MATCHING_FAILURE;
+  }
+
   // If this is %s or %[]
   if (to_conv.conv_name != 'c' && (to_conv.flags & NO_WRITE) == 0) {
     // Always null terminate the string. This may cause a write to the
@@ -70,8 +109,11 @@ int convert_string(Reader<T> *reader, const FormatSection &to_conv) {
     output[i] = '\0';
   }
 
-  if (i == 0)
-    return MATCHING_FAILURE;
+  if ((to_conv.flags & ALLOCATE) != 0) {
+    char **outptr = reinterpret_cast<char **>(to_conv.output_ptr);
+    *outptr = output;
+  }
+
   return READ_OK;
 }
 
diff --git a/libc/test/src/stdio/scanf_core/CMakeLists.txt b/libc/test/src/stdio/scanf_core/CMakeLists.txt
index abf982e4ea00e..07d7f6769d266 100644
--- a/libc/test/src/stdio/scanf_core/CMakeLists.txt
+++ b/libc/test/src/stdio/scanf_core/CMakeLists.txt
@@ -48,6 +48,7 @@ add_libc_unittest(
     libc.src.stdio.scanf_core.converter
     libc.src.stdio.scanf_core.string_reader
     libc.src.__support.CPP.string_view
+    libc.src.__support.CPP.new
   COMPILE_OPTIONS
     ${use_system_file}
 )



More information about the flang-commits mailing list