Skip to content

Conversation

anoopkg6
Copy link
Contributor

@anoopkg6 anoopkg6 commented Oct 6, 2025

Add Dataflow Sanitizer support for SystemZ.

@llvmbot llvmbot added clang Clang issues not falling into any other category compiler-rt clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' compiler-rt:sanitizer llvm:transforms labels Oct 6, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 6, 2025

@llvm/pr-subscribers-compiler-rt-sanitizer

@llvm/pr-subscribers-llvm-transforms

Author: None (anoopkg6)

Changes

Add Dataflow Sanitizer support for SystemZ.


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

10 Files Affected:

  • (modified) clang/lib/Driver/ToolChains/Linux.cpp (+1-1)
  • (modified) compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake (+2-1)
  • (modified) compiler-rt/lib/dfsan/dfsan_allocator.cpp (+16)
  • (modified) compiler-rt/lib/dfsan/dfsan_custom.cpp (+13)
  • (modified) compiler-rt/lib/dfsan/dfsan_platform.h (+14)
  • (modified) compiler-rt/test/dfsan/custom.cpp (+1-1)
  • (modified) compiler-rt/test/dfsan/lit.cfg.py (+6-1)
  • (modified) compiler-rt/test/dfsan/pair.cpp (+24-12)
  • (modified) compiler-rt/test/dfsan/struct.c (+6-6)
  • (modified) llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp (+25-4)
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index 8ac8d4eb91812..6c04b36354dcf 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -819,7 +819,7 @@ SanitizerMask Linux::getSupportedSanitizers() const {
   Res |= SanitizerKind::KernelAddress;
   Res |= SanitizerKind::Vptr;
   Res |= SanitizerKind::SafeStack;
-  if (IsX86_64 || IsMIPS64 || IsAArch64 || IsLoongArch64)
+  if (IsX86_64 || IsMIPS64 || IsAArch64 || IsLoongArch64 || IsSystemZ)
     Res |= SanitizerKind::DataFlow;
   if (IsX86_64 || IsMIPS64 || IsAArch64 || IsX86 || IsArmArch || IsPowerPC64 ||
       IsRISCV64 || IsSystemZ || IsHexagon || IsLoongArch64)
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index ca45d7bd2af7f..2bc695922ef2d 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -35,7 +35,8 @@ set(ALL_ASAN_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM32} ${ARM64} ${RISCV64}
     ${MIPS32} ${MIPS64} ${PPC64} ${S390X} ${SPARC} ${SPARCV9} ${HEXAGON}
     ${LOONGARCH64})
 set(ALL_ASAN_ABI_SUPPORTED_ARCH ${X86_64} ${ARM64} ${ARM64_32})
-set(ALL_DFSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${LOONGARCH64})
+set(ALL_DFSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${LOONGARCH64}
+    ${S390X})
 set(ALL_RTSAN_SUPPORTED_ARCH ${X86_64} ${ARM64})
 
 if(ANDROID)
diff --git a/compiler-rt/lib/dfsan/dfsan_allocator.cpp b/compiler-rt/lib/dfsan/dfsan_allocator.cpp
index 160b1a64d8f6f..700b2fccf9f6c 100644
--- a/compiler-rt/lib/dfsan/dfsan_allocator.cpp
+++ b/compiler-rt/lib/dfsan/dfsan_allocator.cpp
@@ -44,9 +44,24 @@ struct DFsanMapUnmapCallback {
 // duplicated as MappingDesc::ALLOCATOR in dfsan_platform.h.
 #if defined(__aarch64__)
 const uptr kAllocatorSpace = 0xE00000000000ULL;
+#elif defined(__s390x__)
+const uptr kAllocatorSpace = 0x440000000000ULL;
 #else
 const uptr kAllocatorSpace = 0x700000000000ULL;
 #endif
+#if defined(__s390x__)
+const uptr kMaxAllowedMallocSize = 2UL << 30;  // 2G
+
+struct AP64 {  // Allocator64 parameters. Deliberately using a short name.
+  static const uptr kSpaceBeg = kAllocatorSpace;
+  static const uptr kSpaceSize = 0x020000000000;  // 2T.
+  static const uptr kMetadataSize = sizeof(Metadata);
+  using SizeClassMap = DefaultSizeClassMap;
+  using MapUnmapCallback = DFsanMapUnmapCallback;
+  static const uptr kFlags = 0;
+  using AddressSpaceView = LocalAddressSpaceView;
+};
+#else
 const uptr kMaxAllowedMallocSize = 1ULL << 40;
 
 struct AP64 {  // Allocator64 parameters. Deliberately using a short name.
@@ -59,6 +74,7 @@ struct AP64 {  // Allocator64 parameters. Deliberately using a short name.
   using AddressSpaceView = LocalAddressSpaceView;
 };
 
+#endif
 typedef SizeClassAllocator64<AP64> PrimaryAllocator;
 
 typedef CombinedAllocator<PrimaryAllocator> Allocator;
diff --git a/compiler-rt/lib/dfsan/dfsan_custom.cpp b/compiler-rt/lib/dfsan/dfsan_custom.cpp
index dbc00d7ac3ea3..b060e5c56edbe 100644
--- a/compiler-rt/lib/dfsan/dfsan_custom.cpp
+++ b/compiler-rt/lib/dfsan/dfsan_custom.cpp
@@ -2332,7 +2332,20 @@ static int format_buffer(char *str, size_t size, const char *fmt,
         case 'g':
         case 'G':
           if (*(formatter.fmt_cur - 1) == 'L') {
+#if defined(__s390x__)
+            // SystemZ treats float128 argument as an aggregate type and copies
+            // shadow and Origin to passed argument temporary. But passed
+            // argument va_labels and va_origins are zero. Here. we get
+            // Shadow/Origin corresponding to in-memory argument and update
+            // va_labels and va_origins.
+            long double* arg = va_arg(ap, long double*);
+            *va_labels = *shadow_for(arg);
+            if (va_origins != nullptr)
+              *va_origins = *origin_for(arg);
+            retval = formatter.format(*arg);
+#else
             retval = formatter.format(va_arg(ap, long double));
+#endif
           } else {
             retval = formatter.format(va_arg(ap, double));
           }
diff --git a/compiler-rt/lib/dfsan/dfsan_platform.h b/compiler-rt/lib/dfsan/dfsan_platform.h
index 01f0de47d960d..7e6a1dafddb50 100644
--- a/compiler-rt/lib/dfsan/dfsan_platform.h
+++ b/compiler-rt/lib/dfsan/dfsan_platform.h
@@ -67,6 +67,20 @@ const MappingDesc kMemoryLayout[] = {
 };
 #    define MEM_TO_SHADOW(mem) ((uptr)mem ^ 0xB00000000000ULL)
 #    define SHADOW_TO_ORIGIN(shadow) (((uptr)(shadow)) + 0x200000000000ULL)
+#  elif SANITIZER_LINUX && SANITIZER_S390_64
+const MappingDesc kMemoryLayout[] = {
+    {0x000000000000ULL, 0x040000000000ULL, MappingDesc::APP, "low memory"},
+    {0x040000000000ULL, 0x080000000000ULL, MappingDesc::INVALID, "invalid"},
+    {0x080000000000ULL, 0x180000000000ULL, MappingDesc::SHADOW, "shadow"},
+    {0x180000000000ULL, 0x1C0000000000ULL, MappingDesc::INVALID, "invalid"},
+    {0x1C0000000000ULL, 0x2C0000000000ULL, MappingDesc::ORIGIN, "origin"},
+    {0x2C0000000000ULL, 0x440000000000ULL, MappingDesc::INVALID, "invalid"},
+    {0x440000000000ULL, 0x460000000000ULL, MappingDesc::ALLOCATOR, "allocator"},
+    {0x460000000000ULL, 0x500000000000ULL, MappingDesc::APP, "high memory"}};
+
+#    define MEM_TO_SHADOW(mem) \
+      ((((uptr)(mem)) & ~0xC00000000000ULL) + 0x080000000000ULL)
+#    define SHADOW_TO_ORIGIN(shadow) (((uptr)(shadow)) + 0x140000000000ULL)
 
 #  else
 // All of the following configurations are supported.
diff --git a/compiler-rt/test/dfsan/custom.cpp b/compiler-rt/test/dfsan/custom.cpp
index 873af5cd934e2..b4d6b186cb61e 100644
--- a/compiler-rt/test/dfsan/custom.cpp
+++ b/compiler-rt/test/dfsan/custom.cpp
@@ -2240,7 +2240,7 @@ void test_sscanf() {
   strcpy(input_buf, "-559038737");
   test_sscanf_chunk(-559038737, "%d", input_ptr, 1);
   strcpy(input_buf, "3735928559");
-  test_sscanf_chunk(3735928559, "%u", input_ptr, 1);
+  test_sscanf_chunk(3735928559, "%lu", input_ptr, 1);
   strcpy(input_buf, "12345");
   test_sscanf_chunk(12345, "%i", input_ptr, 1);
   strcpy(input_buf, "0751");
diff --git a/compiler-rt/test/dfsan/lit.cfg.py b/compiler-rt/test/dfsan/lit.cfg.py
index e947c51f99a5b..1b4ca6a258bba 100644
--- a/compiler-rt/test/dfsan/lit.cfg.py
+++ b/compiler-rt/test/dfsan/lit.cfg.py
@@ -10,6 +10,8 @@
 
 # Setup default compiler flags used with -fsanitize=dataflow option.
 clang_dfsan_cflags = ["-fsanitize=dataflow"] + [config.target_cflags]
+if config.target_arch == "s390x":
+    clang_dfsan_cflags.append("-mbackchain")
 
 clang_dfsan_cxxflags = config.cxx_mode_flags + clang_dfsan_cflags
 
@@ -25,5 +27,8 @@ def build_invocation(compile_flags):
 config.suffixes = [".c", ".cpp"]
 
 # DataFlowSanitizer tests are currently supported on Linux only.
-if not (config.host_os in ["Linux"] and config.target_arch in ["aarch64", "x86_64", "loongarch64"]):
+if not (
+    config.target_os in ["Linux"]
+    and config.target_arch in ["aarch64", "x86_64", "loongarch64", "s390x"]
+):
     config.unsupported = True
diff --git a/compiler-rt/test/dfsan/pair.cpp b/compiler-rt/test/dfsan/pair.cpp
index 94bbfc72bc649..cb00339e81ec4 100644
--- a/compiler-rt/test/dfsan/pair.cpp
+++ b/compiler-rt/test/dfsan/pair.cpp
@@ -66,8 +66,10 @@ void test_simple_constructors() {
   int *ptr1 = pair1.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i1, sizeof(i1)) == 10);
-  assert(dfsan_read_label(&ptr1, sizeof(ptr1)) == 10);
+  assert(dfsan_read_label(&i1, sizeof(i1)) == 8 ||
+         dfsan_read_label(&i1, sizeof(i1)) == 10);
+  assert(dfsan_read_label(&ptr1, sizeof(ptr1)) == 2 ||
+         dfsan_read_label(&ptr1, sizeof(ptr1)) == 10);
 #else
   assert(dfsan_read_label(&i1, sizeof(i1)) == 8);
   assert(dfsan_read_label(&ptr1, sizeof(ptr1)) == 2);
@@ -78,8 +80,10 @@ void test_simple_constructors() {
   int *ptr2 = pair2.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i2, sizeof(i2)) == 10);
-  assert(dfsan_read_label(&ptr2, sizeof(ptr2)) == 10);
+  assert(dfsan_read_label(&i2, sizeof(i2)) == 8 ||
+         dfsan_read_label(&i2, sizeof(i2)) == 10);
+  assert(dfsan_read_label(&ptr2, sizeof(ptr2)) == 2 ||
+         dfsan_read_label(&ptr2, sizeof(ptr2)) == 10);
 #else
   assert(dfsan_read_label(&i2, sizeof(i2)) == 8);
   assert(dfsan_read_label(&ptr2, sizeof(ptr2)) == 2);
@@ -90,8 +94,10 @@ void test_simple_constructors() {
   int *ptr3 = pair3.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i3, sizeof(i3)) == 10);
-  assert(dfsan_read_label(&ptr3, sizeof(ptr3)) == 10);
+  assert(dfsan_read_label(&i3, sizeof(i3)) == 8 ||
+         dfsan_read_label(&i3, sizeof(i3)) == 10);
+  assert(dfsan_read_label(&ptr3, sizeof(ptr3)) == 2 ||
+         dfsan_read_label(&ptr3, sizeof(ptr3)) == 10);
 #else
   assert(dfsan_read_label(&i3, sizeof(i3)) == 8);
   assert(dfsan_read_label(&ptr3, sizeof(ptr3)) == 2);
@@ -102,8 +108,10 @@ void test_simple_constructors() {
   int *ptr4 = pair4.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i4, sizeof(i4)) == 10);
-  assert(dfsan_read_label(&ptr4, sizeof(ptr4)) == 10);
+  assert(dfsan_read_label(&i4, sizeof(i4)) == 8 ||
+         dfsan_read_label(&i4, sizeof(i4)) == 10);
+  assert(dfsan_read_label(&ptr4, sizeof(ptr4)) == 2 ||
+         dfsan_read_label(&ptr4, sizeof(ptr4)) == 10);
 #else
   assert(dfsan_read_label(&i4, sizeof(i4)) == 8);
   assert(dfsan_read_label(&ptr4, sizeof(ptr4)) == 2);
@@ -140,8 +148,10 @@ void test_branches() {
     {
       std::pair<const char *, uint32_t> r = return_ptr_and_i32(q, res);
 #ifdef O0
-      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 10);
-      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 10);
+      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2 ||
+             dfsan_read_label(&r.first, sizeof(r.first)) == 10);
+      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8 ||
+             dfsan_read_label(&r.second, sizeof(r.second)) == 10);
 #else
       assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2);
       assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8);
@@ -151,8 +161,10 @@ void test_branches() {
     {
       std::pair<const char *, uint64_t> r = return_ptr_and_i64(q, res);
 #ifdef O0
-      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 10);
-      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 10);
+      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2 ||
+             dfsan_read_label(&r.first, sizeof(r.first)) == 10);
+      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8 ||
+             dfsan_read_label(&r.second, sizeof(r.second)) == 10);
 #else
       assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2);
       assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8);
diff --git a/compiler-rt/test/dfsan/struct.c b/compiler-rt/test/dfsan/struct.c
index 7ba0016f7beee..48285e022a98a 100644
--- a/compiler-rt/test/dfsan/struct.c
+++ b/compiler-rt/test/dfsan/struct.c
@@ -48,8 +48,8 @@ int main(void) {
   dfsan_label i1_label = dfsan_read_label(&i1, sizeof(i1));
   dfsan_label ptr1_label = dfsan_read_label(&ptr1, sizeof(ptr1));
 #if defined(O0)
-  assert(i1_label == (i_label | ptr_label));
-  assert(ptr1_label == (i_label | ptr_label));
+  assert(i1_label == i_label || i1_label == (i_label | ptr_label));
+  assert(ptr1_label == ptr_label || ptr1_label == (i_label | ptr_label));
 #else
   assert(i1_label == i_label);
   assert(ptr1_label == ptr_label);
@@ -62,8 +62,8 @@ int main(void) {
   dfsan_label i2_label = dfsan_read_label(&i2, sizeof(i2));
   dfsan_label ptr2_label = dfsan_read_label(&ptr2, sizeof(ptr2));
 #if defined(O0)
-  assert(i2_label == (i_label | ptr_label));
-  assert(ptr2_label == (i_label | ptr_label));
+  assert(i2_label == i_label || i2_label == (i_label | ptr_label));
+  assert(ptr2_label == ptr_label || ptr2_label == (i_label | ptr_label));
 #else
   assert(i2_label == i_label);
   assert(ptr2_label == ptr_label);
@@ -76,8 +76,8 @@ int main(void) {
   dfsan_label i3_label = dfsan_read_label(&i3, sizeof(i3));
   dfsan_label ptr3_label = dfsan_read_label(&ptr3, sizeof(ptr3));
 #if defined(O0)
-  assert(i3_label == (i_label | ptr_label));
-  assert(ptr3_label == (i_label | ptr_label));
+  assert(i3_label == i_label || i3_label == (i_label | ptr_label));
+  assert(ptr3_label == ptr_label || ptr3_label == (i_label | ptr_label));
 #else
   assert(i3_label == i_label);
   assert(ptr3_label == ptr_label);
diff --git a/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
index 61fef1387d82a..64b3f5c812b3b 100644
--- a/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
@@ -313,6 +313,14 @@ const MemoryMapParams Linux_LoongArch64_MemoryMapParams = {
     0x100000000000, // OriginBase
 };
 
+// s390x Linux
+const MemoryMapParams Linux_S390X_MemoryMapParams = {
+    0xC00000000000, // AndMask
+    0,              // XorMask (not used)
+    0x080000000000, // ShadowBase
+    0x1C0000000000, // OriginBase
+};
+
 namespace {
 
 class DFSanABIList {
@@ -1141,6 +1149,9 @@ bool DataFlowSanitizer::initializeModule(Module &M) {
   case Triple::loongarch64:
     MapParams = &Linux_LoongArch64_MemoryMapParams;
     break;
+  case Triple::systemz:
+    MapParams = &Linux_S390X_MemoryMapParams;
+    break;
   default:
     report_fatal_error("unsupported architecture");
   }
@@ -1951,8 +1962,12 @@ Value *DataFlowSanitizer::getShadowAddress(Value *Addr,
 Value *DataFlowSanitizer::getShadowAddress(Value *Addr,
                                            BasicBlock::iterator Pos) {
   IRBuilder<> IRB(Pos->getParent(), Pos);
-  Value *ShadowOffset = getShadowOffset(Addr, IRB);
-  return getShadowAddress(Addr, Pos, ShadowOffset);
+  Value *ShadowAddr = getShadowOffset(Addr, IRB);
+  uint64_t ShadowBase = MapParams->ShadowBase;
+  if (ShadowBase != 0)
+    ShadowAddr =
+        IRB.CreateAdd(ShadowAddr, ConstantInt::get(IntptrTy, ShadowBase));
+  return getShadowAddress(Addr, Pos, ShadowAddr);
 }
 
 Value *DFSanFunction::combineShadowsThenConvert(Type *T, Value *V1, Value *V2,
@@ -2181,8 +2196,14 @@ std::pair<Value *, Value *> DFSanFunction::loadShadowFast(
       // and then the entire shadow for the second origin pointer (which will be
       // chosen by combineOrigins() iff the least-significant half of the wide
       // shadow was empty but the other half was not).
-      Value *WideShadowLo = IRB.CreateShl(
-          WideShadow, ConstantInt::get(WideShadowTy, WideShadowBitWidth / 2));
+      Value *WideShadowLo =
+          F->getParent()->getDataLayout().isLittleEndian()
+              ? IRB.CreateShl(
+                    WideShadow,
+                    ConstantInt::get(WideShadowTy, WideShadowBitWidth / 2))
+              : IRB.CreateAnd(
+                    WideShadow,
+                    ConstantInt::get(WideShadowTy, 0xFFFFFFFF00000000ULL));
       Shadows.push_back(WideShadow);
       Origins.push_back(DFS.loadNextOrigin(Pos, OriginAlign, &OriginAddr));
 

@llvmbot
Copy link
Member

llvmbot commented Oct 6, 2025

@llvm/pr-subscribers-clang

Author: None (anoopkg6)

Changes

Add Dataflow Sanitizer support for SystemZ.


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

10 Files Affected:

  • (modified) clang/lib/Driver/ToolChains/Linux.cpp (+1-1)
  • (modified) compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake (+2-1)
  • (modified) compiler-rt/lib/dfsan/dfsan_allocator.cpp (+16)
  • (modified) compiler-rt/lib/dfsan/dfsan_custom.cpp (+13)
  • (modified) compiler-rt/lib/dfsan/dfsan_platform.h (+14)
  • (modified) compiler-rt/test/dfsan/custom.cpp (+1-1)
  • (modified) compiler-rt/test/dfsan/lit.cfg.py (+6-1)
  • (modified) compiler-rt/test/dfsan/pair.cpp (+24-12)
  • (modified) compiler-rt/test/dfsan/struct.c (+6-6)
  • (modified) llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp (+25-4)
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index 8ac8d4eb91812..6c04b36354dcf 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -819,7 +819,7 @@ SanitizerMask Linux::getSupportedSanitizers() const {
   Res |= SanitizerKind::KernelAddress;
   Res |= SanitizerKind::Vptr;
   Res |= SanitizerKind::SafeStack;
-  if (IsX86_64 || IsMIPS64 || IsAArch64 || IsLoongArch64)
+  if (IsX86_64 || IsMIPS64 || IsAArch64 || IsLoongArch64 || IsSystemZ)
     Res |= SanitizerKind::DataFlow;
   if (IsX86_64 || IsMIPS64 || IsAArch64 || IsX86 || IsArmArch || IsPowerPC64 ||
       IsRISCV64 || IsSystemZ || IsHexagon || IsLoongArch64)
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index ca45d7bd2af7f..2bc695922ef2d 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -35,7 +35,8 @@ set(ALL_ASAN_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM32} ${ARM64} ${RISCV64}
     ${MIPS32} ${MIPS64} ${PPC64} ${S390X} ${SPARC} ${SPARCV9} ${HEXAGON}
     ${LOONGARCH64})
 set(ALL_ASAN_ABI_SUPPORTED_ARCH ${X86_64} ${ARM64} ${ARM64_32})
-set(ALL_DFSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${LOONGARCH64})
+set(ALL_DFSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${LOONGARCH64}
+    ${S390X})
 set(ALL_RTSAN_SUPPORTED_ARCH ${X86_64} ${ARM64})
 
 if(ANDROID)
diff --git a/compiler-rt/lib/dfsan/dfsan_allocator.cpp b/compiler-rt/lib/dfsan/dfsan_allocator.cpp
index 160b1a64d8f6f..700b2fccf9f6c 100644
--- a/compiler-rt/lib/dfsan/dfsan_allocator.cpp
+++ b/compiler-rt/lib/dfsan/dfsan_allocator.cpp
@@ -44,9 +44,24 @@ struct DFsanMapUnmapCallback {
 // duplicated as MappingDesc::ALLOCATOR in dfsan_platform.h.
 #if defined(__aarch64__)
 const uptr kAllocatorSpace = 0xE00000000000ULL;
+#elif defined(__s390x__)
+const uptr kAllocatorSpace = 0x440000000000ULL;
 #else
 const uptr kAllocatorSpace = 0x700000000000ULL;
 #endif
+#if defined(__s390x__)
+const uptr kMaxAllowedMallocSize = 2UL << 30;  // 2G
+
+struct AP64 {  // Allocator64 parameters. Deliberately using a short name.
+  static const uptr kSpaceBeg = kAllocatorSpace;
+  static const uptr kSpaceSize = 0x020000000000;  // 2T.
+  static const uptr kMetadataSize = sizeof(Metadata);
+  using SizeClassMap = DefaultSizeClassMap;
+  using MapUnmapCallback = DFsanMapUnmapCallback;
+  static const uptr kFlags = 0;
+  using AddressSpaceView = LocalAddressSpaceView;
+};
+#else
 const uptr kMaxAllowedMallocSize = 1ULL << 40;
 
 struct AP64 {  // Allocator64 parameters. Deliberately using a short name.
@@ -59,6 +74,7 @@ struct AP64 {  // Allocator64 parameters. Deliberately using a short name.
   using AddressSpaceView = LocalAddressSpaceView;
 };
 
+#endif
 typedef SizeClassAllocator64<AP64> PrimaryAllocator;
 
 typedef CombinedAllocator<PrimaryAllocator> Allocator;
diff --git a/compiler-rt/lib/dfsan/dfsan_custom.cpp b/compiler-rt/lib/dfsan/dfsan_custom.cpp
index dbc00d7ac3ea3..b060e5c56edbe 100644
--- a/compiler-rt/lib/dfsan/dfsan_custom.cpp
+++ b/compiler-rt/lib/dfsan/dfsan_custom.cpp
@@ -2332,7 +2332,20 @@ static int format_buffer(char *str, size_t size, const char *fmt,
         case 'g':
         case 'G':
           if (*(formatter.fmt_cur - 1) == 'L') {
+#if defined(__s390x__)
+            // SystemZ treats float128 argument as an aggregate type and copies
+            // shadow and Origin to passed argument temporary. But passed
+            // argument va_labels and va_origins are zero. Here. we get
+            // Shadow/Origin corresponding to in-memory argument and update
+            // va_labels and va_origins.
+            long double* arg = va_arg(ap, long double*);
+            *va_labels = *shadow_for(arg);
+            if (va_origins != nullptr)
+              *va_origins = *origin_for(arg);
+            retval = formatter.format(*arg);
+#else
             retval = formatter.format(va_arg(ap, long double));
+#endif
           } else {
             retval = formatter.format(va_arg(ap, double));
           }
diff --git a/compiler-rt/lib/dfsan/dfsan_platform.h b/compiler-rt/lib/dfsan/dfsan_platform.h
index 01f0de47d960d..7e6a1dafddb50 100644
--- a/compiler-rt/lib/dfsan/dfsan_platform.h
+++ b/compiler-rt/lib/dfsan/dfsan_platform.h
@@ -67,6 +67,20 @@ const MappingDesc kMemoryLayout[] = {
 };
 #    define MEM_TO_SHADOW(mem) ((uptr)mem ^ 0xB00000000000ULL)
 #    define SHADOW_TO_ORIGIN(shadow) (((uptr)(shadow)) + 0x200000000000ULL)
+#  elif SANITIZER_LINUX && SANITIZER_S390_64
+const MappingDesc kMemoryLayout[] = {
+    {0x000000000000ULL, 0x040000000000ULL, MappingDesc::APP, "low memory"},
+    {0x040000000000ULL, 0x080000000000ULL, MappingDesc::INVALID, "invalid"},
+    {0x080000000000ULL, 0x180000000000ULL, MappingDesc::SHADOW, "shadow"},
+    {0x180000000000ULL, 0x1C0000000000ULL, MappingDesc::INVALID, "invalid"},
+    {0x1C0000000000ULL, 0x2C0000000000ULL, MappingDesc::ORIGIN, "origin"},
+    {0x2C0000000000ULL, 0x440000000000ULL, MappingDesc::INVALID, "invalid"},
+    {0x440000000000ULL, 0x460000000000ULL, MappingDesc::ALLOCATOR, "allocator"},
+    {0x460000000000ULL, 0x500000000000ULL, MappingDesc::APP, "high memory"}};
+
+#    define MEM_TO_SHADOW(mem) \
+      ((((uptr)(mem)) & ~0xC00000000000ULL) + 0x080000000000ULL)
+#    define SHADOW_TO_ORIGIN(shadow) (((uptr)(shadow)) + 0x140000000000ULL)
 
 #  else
 // All of the following configurations are supported.
diff --git a/compiler-rt/test/dfsan/custom.cpp b/compiler-rt/test/dfsan/custom.cpp
index 873af5cd934e2..b4d6b186cb61e 100644
--- a/compiler-rt/test/dfsan/custom.cpp
+++ b/compiler-rt/test/dfsan/custom.cpp
@@ -2240,7 +2240,7 @@ void test_sscanf() {
   strcpy(input_buf, "-559038737");
   test_sscanf_chunk(-559038737, "%d", input_ptr, 1);
   strcpy(input_buf, "3735928559");
-  test_sscanf_chunk(3735928559, "%u", input_ptr, 1);
+  test_sscanf_chunk(3735928559, "%lu", input_ptr, 1);
   strcpy(input_buf, "12345");
   test_sscanf_chunk(12345, "%i", input_ptr, 1);
   strcpy(input_buf, "0751");
diff --git a/compiler-rt/test/dfsan/lit.cfg.py b/compiler-rt/test/dfsan/lit.cfg.py
index e947c51f99a5b..1b4ca6a258bba 100644
--- a/compiler-rt/test/dfsan/lit.cfg.py
+++ b/compiler-rt/test/dfsan/lit.cfg.py
@@ -10,6 +10,8 @@
 
 # Setup default compiler flags used with -fsanitize=dataflow option.
 clang_dfsan_cflags = ["-fsanitize=dataflow"] + [config.target_cflags]
+if config.target_arch == "s390x":
+    clang_dfsan_cflags.append("-mbackchain")
 
 clang_dfsan_cxxflags = config.cxx_mode_flags + clang_dfsan_cflags
 
@@ -25,5 +27,8 @@ def build_invocation(compile_flags):
 config.suffixes = [".c", ".cpp"]
 
 # DataFlowSanitizer tests are currently supported on Linux only.
-if not (config.host_os in ["Linux"] and config.target_arch in ["aarch64", "x86_64", "loongarch64"]):
+if not (
+    config.target_os in ["Linux"]
+    and config.target_arch in ["aarch64", "x86_64", "loongarch64", "s390x"]
+):
     config.unsupported = True
diff --git a/compiler-rt/test/dfsan/pair.cpp b/compiler-rt/test/dfsan/pair.cpp
index 94bbfc72bc649..cb00339e81ec4 100644
--- a/compiler-rt/test/dfsan/pair.cpp
+++ b/compiler-rt/test/dfsan/pair.cpp
@@ -66,8 +66,10 @@ void test_simple_constructors() {
   int *ptr1 = pair1.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i1, sizeof(i1)) == 10);
-  assert(dfsan_read_label(&ptr1, sizeof(ptr1)) == 10);
+  assert(dfsan_read_label(&i1, sizeof(i1)) == 8 ||
+         dfsan_read_label(&i1, sizeof(i1)) == 10);
+  assert(dfsan_read_label(&ptr1, sizeof(ptr1)) == 2 ||
+         dfsan_read_label(&ptr1, sizeof(ptr1)) == 10);
 #else
   assert(dfsan_read_label(&i1, sizeof(i1)) == 8);
   assert(dfsan_read_label(&ptr1, sizeof(ptr1)) == 2);
@@ -78,8 +80,10 @@ void test_simple_constructors() {
   int *ptr2 = pair2.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i2, sizeof(i2)) == 10);
-  assert(dfsan_read_label(&ptr2, sizeof(ptr2)) == 10);
+  assert(dfsan_read_label(&i2, sizeof(i2)) == 8 ||
+         dfsan_read_label(&i2, sizeof(i2)) == 10);
+  assert(dfsan_read_label(&ptr2, sizeof(ptr2)) == 2 ||
+         dfsan_read_label(&ptr2, sizeof(ptr2)) == 10);
 #else
   assert(dfsan_read_label(&i2, sizeof(i2)) == 8);
   assert(dfsan_read_label(&ptr2, sizeof(ptr2)) == 2);
@@ -90,8 +94,10 @@ void test_simple_constructors() {
   int *ptr3 = pair3.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i3, sizeof(i3)) == 10);
-  assert(dfsan_read_label(&ptr3, sizeof(ptr3)) == 10);
+  assert(dfsan_read_label(&i3, sizeof(i3)) == 8 ||
+         dfsan_read_label(&i3, sizeof(i3)) == 10);
+  assert(dfsan_read_label(&ptr3, sizeof(ptr3)) == 2 ||
+         dfsan_read_label(&ptr3, sizeof(ptr3)) == 10);
 #else
   assert(dfsan_read_label(&i3, sizeof(i3)) == 8);
   assert(dfsan_read_label(&ptr3, sizeof(ptr3)) == 2);
@@ -102,8 +108,10 @@ void test_simple_constructors() {
   int *ptr4 = pair4.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i4, sizeof(i4)) == 10);
-  assert(dfsan_read_label(&ptr4, sizeof(ptr4)) == 10);
+  assert(dfsan_read_label(&i4, sizeof(i4)) == 8 ||
+         dfsan_read_label(&i4, sizeof(i4)) == 10);
+  assert(dfsan_read_label(&ptr4, sizeof(ptr4)) == 2 ||
+         dfsan_read_label(&ptr4, sizeof(ptr4)) == 10);
 #else
   assert(dfsan_read_label(&i4, sizeof(i4)) == 8);
   assert(dfsan_read_label(&ptr4, sizeof(ptr4)) == 2);
@@ -140,8 +148,10 @@ void test_branches() {
     {
       std::pair<const char *, uint32_t> r = return_ptr_and_i32(q, res);
 #ifdef O0
-      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 10);
-      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 10);
+      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2 ||
+             dfsan_read_label(&r.first, sizeof(r.first)) == 10);
+      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8 ||
+             dfsan_read_label(&r.second, sizeof(r.second)) == 10);
 #else
       assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2);
       assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8);
@@ -151,8 +161,10 @@ void test_branches() {
     {
       std::pair<const char *, uint64_t> r = return_ptr_and_i64(q, res);
 #ifdef O0
-      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 10);
-      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 10);
+      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2 ||
+             dfsan_read_label(&r.first, sizeof(r.first)) == 10);
+      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8 ||
+             dfsan_read_label(&r.second, sizeof(r.second)) == 10);
 #else
       assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2);
       assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8);
diff --git a/compiler-rt/test/dfsan/struct.c b/compiler-rt/test/dfsan/struct.c
index 7ba0016f7beee..48285e022a98a 100644
--- a/compiler-rt/test/dfsan/struct.c
+++ b/compiler-rt/test/dfsan/struct.c
@@ -48,8 +48,8 @@ int main(void) {
   dfsan_label i1_label = dfsan_read_label(&i1, sizeof(i1));
   dfsan_label ptr1_label = dfsan_read_label(&ptr1, sizeof(ptr1));
 #if defined(O0)
-  assert(i1_label == (i_label | ptr_label));
-  assert(ptr1_label == (i_label | ptr_label));
+  assert(i1_label == i_label || i1_label == (i_label | ptr_label));
+  assert(ptr1_label == ptr_label || ptr1_label == (i_label | ptr_label));
 #else
   assert(i1_label == i_label);
   assert(ptr1_label == ptr_label);
@@ -62,8 +62,8 @@ int main(void) {
   dfsan_label i2_label = dfsan_read_label(&i2, sizeof(i2));
   dfsan_label ptr2_label = dfsan_read_label(&ptr2, sizeof(ptr2));
 #if defined(O0)
-  assert(i2_label == (i_label | ptr_label));
-  assert(ptr2_label == (i_label | ptr_label));
+  assert(i2_label == i_label || i2_label == (i_label | ptr_label));
+  assert(ptr2_label == ptr_label || ptr2_label == (i_label | ptr_label));
 #else
   assert(i2_label == i_label);
   assert(ptr2_label == ptr_label);
@@ -76,8 +76,8 @@ int main(void) {
   dfsan_label i3_label = dfsan_read_label(&i3, sizeof(i3));
   dfsan_label ptr3_label = dfsan_read_label(&ptr3, sizeof(ptr3));
 #if defined(O0)
-  assert(i3_label == (i_label | ptr_label));
-  assert(ptr3_label == (i_label | ptr_label));
+  assert(i3_label == i_label || i3_label == (i_label | ptr_label));
+  assert(ptr3_label == ptr_label || ptr3_label == (i_label | ptr_label));
 #else
   assert(i3_label == i_label);
   assert(ptr3_label == ptr_label);
diff --git a/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
index 61fef1387d82a..64b3f5c812b3b 100644
--- a/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
@@ -313,6 +313,14 @@ const MemoryMapParams Linux_LoongArch64_MemoryMapParams = {
     0x100000000000, // OriginBase
 };
 
+// s390x Linux
+const MemoryMapParams Linux_S390X_MemoryMapParams = {
+    0xC00000000000, // AndMask
+    0,              // XorMask (not used)
+    0x080000000000, // ShadowBase
+    0x1C0000000000, // OriginBase
+};
+
 namespace {
 
 class DFSanABIList {
@@ -1141,6 +1149,9 @@ bool DataFlowSanitizer::initializeModule(Module &M) {
   case Triple::loongarch64:
     MapParams = &Linux_LoongArch64_MemoryMapParams;
     break;
+  case Triple::systemz:
+    MapParams = &Linux_S390X_MemoryMapParams;
+    break;
   default:
     report_fatal_error("unsupported architecture");
   }
@@ -1951,8 +1962,12 @@ Value *DataFlowSanitizer::getShadowAddress(Value *Addr,
 Value *DataFlowSanitizer::getShadowAddress(Value *Addr,
                                            BasicBlock::iterator Pos) {
   IRBuilder<> IRB(Pos->getParent(), Pos);
-  Value *ShadowOffset = getShadowOffset(Addr, IRB);
-  return getShadowAddress(Addr, Pos, ShadowOffset);
+  Value *ShadowAddr = getShadowOffset(Addr, IRB);
+  uint64_t ShadowBase = MapParams->ShadowBase;
+  if (ShadowBase != 0)
+    ShadowAddr =
+        IRB.CreateAdd(ShadowAddr, ConstantInt::get(IntptrTy, ShadowBase));
+  return getShadowAddress(Addr, Pos, ShadowAddr);
 }
 
 Value *DFSanFunction::combineShadowsThenConvert(Type *T, Value *V1, Value *V2,
@@ -2181,8 +2196,14 @@ std::pair<Value *, Value *> DFSanFunction::loadShadowFast(
       // and then the entire shadow for the second origin pointer (which will be
       // chosen by combineOrigins() iff the least-significant half of the wide
       // shadow was empty but the other half was not).
-      Value *WideShadowLo = IRB.CreateShl(
-          WideShadow, ConstantInt::get(WideShadowTy, WideShadowBitWidth / 2));
+      Value *WideShadowLo =
+          F->getParent()->getDataLayout().isLittleEndian()
+              ? IRB.CreateShl(
+                    WideShadow,
+                    ConstantInt::get(WideShadowTy, WideShadowBitWidth / 2))
+              : IRB.CreateAnd(
+                    WideShadow,
+                    ConstantInt::get(WideShadowTy, 0xFFFFFFFF00000000ULL));
       Shadows.push_back(WideShadow);
       Origins.push_back(DFS.loadNextOrigin(Pos, OriginAlign, &OriginAddr));
 

@llvmbot
Copy link
Member

llvmbot commented Oct 6, 2025

@llvm/pr-subscribers-clang-driver

Author: None (anoopkg6)

Changes

Add Dataflow Sanitizer support for SystemZ.


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

10 Files Affected:

  • (modified) clang/lib/Driver/ToolChains/Linux.cpp (+1-1)
  • (modified) compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake (+2-1)
  • (modified) compiler-rt/lib/dfsan/dfsan_allocator.cpp (+16)
  • (modified) compiler-rt/lib/dfsan/dfsan_custom.cpp (+13)
  • (modified) compiler-rt/lib/dfsan/dfsan_platform.h (+14)
  • (modified) compiler-rt/test/dfsan/custom.cpp (+1-1)
  • (modified) compiler-rt/test/dfsan/lit.cfg.py (+6-1)
  • (modified) compiler-rt/test/dfsan/pair.cpp (+24-12)
  • (modified) compiler-rt/test/dfsan/struct.c (+6-6)
  • (modified) llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp (+25-4)
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index 8ac8d4eb91812..6c04b36354dcf 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -819,7 +819,7 @@ SanitizerMask Linux::getSupportedSanitizers() const {
   Res |= SanitizerKind::KernelAddress;
   Res |= SanitizerKind::Vptr;
   Res |= SanitizerKind::SafeStack;
-  if (IsX86_64 || IsMIPS64 || IsAArch64 || IsLoongArch64)
+  if (IsX86_64 || IsMIPS64 || IsAArch64 || IsLoongArch64 || IsSystemZ)
     Res |= SanitizerKind::DataFlow;
   if (IsX86_64 || IsMIPS64 || IsAArch64 || IsX86 || IsArmArch || IsPowerPC64 ||
       IsRISCV64 || IsSystemZ || IsHexagon || IsLoongArch64)
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index ca45d7bd2af7f..2bc695922ef2d 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -35,7 +35,8 @@ set(ALL_ASAN_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM32} ${ARM64} ${RISCV64}
     ${MIPS32} ${MIPS64} ${PPC64} ${S390X} ${SPARC} ${SPARCV9} ${HEXAGON}
     ${LOONGARCH64})
 set(ALL_ASAN_ABI_SUPPORTED_ARCH ${X86_64} ${ARM64} ${ARM64_32})
-set(ALL_DFSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${LOONGARCH64})
+set(ALL_DFSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${LOONGARCH64}
+    ${S390X})
 set(ALL_RTSAN_SUPPORTED_ARCH ${X86_64} ${ARM64})
 
 if(ANDROID)
diff --git a/compiler-rt/lib/dfsan/dfsan_allocator.cpp b/compiler-rt/lib/dfsan/dfsan_allocator.cpp
index 160b1a64d8f6f..700b2fccf9f6c 100644
--- a/compiler-rt/lib/dfsan/dfsan_allocator.cpp
+++ b/compiler-rt/lib/dfsan/dfsan_allocator.cpp
@@ -44,9 +44,24 @@ struct DFsanMapUnmapCallback {
 // duplicated as MappingDesc::ALLOCATOR in dfsan_platform.h.
 #if defined(__aarch64__)
 const uptr kAllocatorSpace = 0xE00000000000ULL;
+#elif defined(__s390x__)
+const uptr kAllocatorSpace = 0x440000000000ULL;
 #else
 const uptr kAllocatorSpace = 0x700000000000ULL;
 #endif
+#if defined(__s390x__)
+const uptr kMaxAllowedMallocSize = 2UL << 30;  // 2G
+
+struct AP64 {  // Allocator64 parameters. Deliberately using a short name.
+  static const uptr kSpaceBeg = kAllocatorSpace;
+  static const uptr kSpaceSize = 0x020000000000;  // 2T.
+  static const uptr kMetadataSize = sizeof(Metadata);
+  using SizeClassMap = DefaultSizeClassMap;
+  using MapUnmapCallback = DFsanMapUnmapCallback;
+  static const uptr kFlags = 0;
+  using AddressSpaceView = LocalAddressSpaceView;
+};
+#else
 const uptr kMaxAllowedMallocSize = 1ULL << 40;
 
 struct AP64 {  // Allocator64 parameters. Deliberately using a short name.
@@ -59,6 +74,7 @@ struct AP64 {  // Allocator64 parameters. Deliberately using a short name.
   using AddressSpaceView = LocalAddressSpaceView;
 };
 
+#endif
 typedef SizeClassAllocator64<AP64> PrimaryAllocator;
 
 typedef CombinedAllocator<PrimaryAllocator> Allocator;
diff --git a/compiler-rt/lib/dfsan/dfsan_custom.cpp b/compiler-rt/lib/dfsan/dfsan_custom.cpp
index dbc00d7ac3ea3..b060e5c56edbe 100644
--- a/compiler-rt/lib/dfsan/dfsan_custom.cpp
+++ b/compiler-rt/lib/dfsan/dfsan_custom.cpp
@@ -2332,7 +2332,20 @@ static int format_buffer(char *str, size_t size, const char *fmt,
         case 'g':
         case 'G':
           if (*(formatter.fmt_cur - 1) == 'L') {
+#if defined(__s390x__)
+            // SystemZ treats float128 argument as an aggregate type and copies
+            // shadow and Origin to passed argument temporary. But passed
+            // argument va_labels and va_origins are zero. Here. we get
+            // Shadow/Origin corresponding to in-memory argument and update
+            // va_labels and va_origins.
+            long double* arg = va_arg(ap, long double*);
+            *va_labels = *shadow_for(arg);
+            if (va_origins != nullptr)
+              *va_origins = *origin_for(arg);
+            retval = formatter.format(*arg);
+#else
             retval = formatter.format(va_arg(ap, long double));
+#endif
           } else {
             retval = formatter.format(va_arg(ap, double));
           }
diff --git a/compiler-rt/lib/dfsan/dfsan_platform.h b/compiler-rt/lib/dfsan/dfsan_platform.h
index 01f0de47d960d..7e6a1dafddb50 100644
--- a/compiler-rt/lib/dfsan/dfsan_platform.h
+++ b/compiler-rt/lib/dfsan/dfsan_platform.h
@@ -67,6 +67,20 @@ const MappingDesc kMemoryLayout[] = {
 };
 #    define MEM_TO_SHADOW(mem) ((uptr)mem ^ 0xB00000000000ULL)
 #    define SHADOW_TO_ORIGIN(shadow) (((uptr)(shadow)) + 0x200000000000ULL)
+#  elif SANITIZER_LINUX && SANITIZER_S390_64
+const MappingDesc kMemoryLayout[] = {
+    {0x000000000000ULL, 0x040000000000ULL, MappingDesc::APP, "low memory"},
+    {0x040000000000ULL, 0x080000000000ULL, MappingDesc::INVALID, "invalid"},
+    {0x080000000000ULL, 0x180000000000ULL, MappingDesc::SHADOW, "shadow"},
+    {0x180000000000ULL, 0x1C0000000000ULL, MappingDesc::INVALID, "invalid"},
+    {0x1C0000000000ULL, 0x2C0000000000ULL, MappingDesc::ORIGIN, "origin"},
+    {0x2C0000000000ULL, 0x440000000000ULL, MappingDesc::INVALID, "invalid"},
+    {0x440000000000ULL, 0x460000000000ULL, MappingDesc::ALLOCATOR, "allocator"},
+    {0x460000000000ULL, 0x500000000000ULL, MappingDesc::APP, "high memory"}};
+
+#    define MEM_TO_SHADOW(mem) \
+      ((((uptr)(mem)) & ~0xC00000000000ULL) + 0x080000000000ULL)
+#    define SHADOW_TO_ORIGIN(shadow) (((uptr)(shadow)) + 0x140000000000ULL)
 
 #  else
 // All of the following configurations are supported.
diff --git a/compiler-rt/test/dfsan/custom.cpp b/compiler-rt/test/dfsan/custom.cpp
index 873af5cd934e2..b4d6b186cb61e 100644
--- a/compiler-rt/test/dfsan/custom.cpp
+++ b/compiler-rt/test/dfsan/custom.cpp
@@ -2240,7 +2240,7 @@ void test_sscanf() {
   strcpy(input_buf, "-559038737");
   test_sscanf_chunk(-559038737, "%d", input_ptr, 1);
   strcpy(input_buf, "3735928559");
-  test_sscanf_chunk(3735928559, "%u", input_ptr, 1);
+  test_sscanf_chunk(3735928559, "%lu", input_ptr, 1);
   strcpy(input_buf, "12345");
   test_sscanf_chunk(12345, "%i", input_ptr, 1);
   strcpy(input_buf, "0751");
diff --git a/compiler-rt/test/dfsan/lit.cfg.py b/compiler-rt/test/dfsan/lit.cfg.py
index e947c51f99a5b..1b4ca6a258bba 100644
--- a/compiler-rt/test/dfsan/lit.cfg.py
+++ b/compiler-rt/test/dfsan/lit.cfg.py
@@ -10,6 +10,8 @@
 
 # Setup default compiler flags used with -fsanitize=dataflow option.
 clang_dfsan_cflags = ["-fsanitize=dataflow"] + [config.target_cflags]
+if config.target_arch == "s390x":
+    clang_dfsan_cflags.append("-mbackchain")
 
 clang_dfsan_cxxflags = config.cxx_mode_flags + clang_dfsan_cflags
 
@@ -25,5 +27,8 @@ def build_invocation(compile_flags):
 config.suffixes = [".c", ".cpp"]
 
 # DataFlowSanitizer tests are currently supported on Linux only.
-if not (config.host_os in ["Linux"] and config.target_arch in ["aarch64", "x86_64", "loongarch64"]):
+if not (
+    config.target_os in ["Linux"]
+    and config.target_arch in ["aarch64", "x86_64", "loongarch64", "s390x"]
+):
     config.unsupported = True
diff --git a/compiler-rt/test/dfsan/pair.cpp b/compiler-rt/test/dfsan/pair.cpp
index 94bbfc72bc649..cb00339e81ec4 100644
--- a/compiler-rt/test/dfsan/pair.cpp
+++ b/compiler-rt/test/dfsan/pair.cpp
@@ -66,8 +66,10 @@ void test_simple_constructors() {
   int *ptr1 = pair1.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i1, sizeof(i1)) == 10);
-  assert(dfsan_read_label(&ptr1, sizeof(ptr1)) == 10);
+  assert(dfsan_read_label(&i1, sizeof(i1)) == 8 ||
+         dfsan_read_label(&i1, sizeof(i1)) == 10);
+  assert(dfsan_read_label(&ptr1, sizeof(ptr1)) == 2 ||
+         dfsan_read_label(&ptr1, sizeof(ptr1)) == 10);
 #else
   assert(dfsan_read_label(&i1, sizeof(i1)) == 8);
   assert(dfsan_read_label(&ptr1, sizeof(ptr1)) == 2);
@@ -78,8 +80,10 @@ void test_simple_constructors() {
   int *ptr2 = pair2.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i2, sizeof(i2)) == 10);
-  assert(dfsan_read_label(&ptr2, sizeof(ptr2)) == 10);
+  assert(dfsan_read_label(&i2, sizeof(i2)) == 8 ||
+         dfsan_read_label(&i2, sizeof(i2)) == 10);
+  assert(dfsan_read_label(&ptr2, sizeof(ptr2)) == 2 ||
+         dfsan_read_label(&ptr2, sizeof(ptr2)) == 10);
 #else
   assert(dfsan_read_label(&i2, sizeof(i2)) == 8);
   assert(dfsan_read_label(&ptr2, sizeof(ptr2)) == 2);
@@ -90,8 +94,10 @@ void test_simple_constructors() {
   int *ptr3 = pair3.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i3, sizeof(i3)) == 10);
-  assert(dfsan_read_label(&ptr3, sizeof(ptr3)) == 10);
+  assert(dfsan_read_label(&i3, sizeof(i3)) == 8 ||
+         dfsan_read_label(&i3, sizeof(i3)) == 10);
+  assert(dfsan_read_label(&ptr3, sizeof(ptr3)) == 2 ||
+         dfsan_read_label(&ptr3, sizeof(ptr3)) == 10);
 #else
   assert(dfsan_read_label(&i3, sizeof(i3)) == 8);
   assert(dfsan_read_label(&ptr3, sizeof(ptr3)) == 2);
@@ -102,8 +108,10 @@ void test_simple_constructors() {
   int *ptr4 = pair4.first;
 
 #ifdef O0
-  assert(dfsan_read_label(&i4, sizeof(i4)) == 10);
-  assert(dfsan_read_label(&ptr4, sizeof(ptr4)) == 10);
+  assert(dfsan_read_label(&i4, sizeof(i4)) == 8 ||
+         dfsan_read_label(&i4, sizeof(i4)) == 10);
+  assert(dfsan_read_label(&ptr4, sizeof(ptr4)) == 2 ||
+         dfsan_read_label(&ptr4, sizeof(ptr4)) == 10);
 #else
   assert(dfsan_read_label(&i4, sizeof(i4)) == 8);
   assert(dfsan_read_label(&ptr4, sizeof(ptr4)) == 2);
@@ -140,8 +148,10 @@ void test_branches() {
     {
       std::pair<const char *, uint32_t> r = return_ptr_and_i32(q, res);
 #ifdef O0
-      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 10);
-      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 10);
+      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2 ||
+             dfsan_read_label(&r.first, sizeof(r.first)) == 10);
+      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8 ||
+             dfsan_read_label(&r.second, sizeof(r.second)) == 10);
 #else
       assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2);
       assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8);
@@ -151,8 +161,10 @@ void test_branches() {
     {
       std::pair<const char *, uint64_t> r = return_ptr_and_i64(q, res);
 #ifdef O0
-      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 10);
-      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 10);
+      assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2 ||
+             dfsan_read_label(&r.first, sizeof(r.first)) == 10);
+      assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8 ||
+             dfsan_read_label(&r.second, sizeof(r.second)) == 10);
 #else
       assert(dfsan_read_label(&r.first, sizeof(r.first)) == 2);
       assert(dfsan_read_label(&r.second, sizeof(r.second)) == 8);
diff --git a/compiler-rt/test/dfsan/struct.c b/compiler-rt/test/dfsan/struct.c
index 7ba0016f7beee..48285e022a98a 100644
--- a/compiler-rt/test/dfsan/struct.c
+++ b/compiler-rt/test/dfsan/struct.c
@@ -48,8 +48,8 @@ int main(void) {
   dfsan_label i1_label = dfsan_read_label(&i1, sizeof(i1));
   dfsan_label ptr1_label = dfsan_read_label(&ptr1, sizeof(ptr1));
 #if defined(O0)
-  assert(i1_label == (i_label | ptr_label));
-  assert(ptr1_label == (i_label | ptr_label));
+  assert(i1_label == i_label || i1_label == (i_label | ptr_label));
+  assert(ptr1_label == ptr_label || ptr1_label == (i_label | ptr_label));
 #else
   assert(i1_label == i_label);
   assert(ptr1_label == ptr_label);
@@ -62,8 +62,8 @@ int main(void) {
   dfsan_label i2_label = dfsan_read_label(&i2, sizeof(i2));
   dfsan_label ptr2_label = dfsan_read_label(&ptr2, sizeof(ptr2));
 #if defined(O0)
-  assert(i2_label == (i_label | ptr_label));
-  assert(ptr2_label == (i_label | ptr_label));
+  assert(i2_label == i_label || i2_label == (i_label | ptr_label));
+  assert(ptr2_label == ptr_label || ptr2_label == (i_label | ptr_label));
 #else
   assert(i2_label == i_label);
   assert(ptr2_label == ptr_label);
@@ -76,8 +76,8 @@ int main(void) {
   dfsan_label i3_label = dfsan_read_label(&i3, sizeof(i3));
   dfsan_label ptr3_label = dfsan_read_label(&ptr3, sizeof(ptr3));
 #if defined(O0)
-  assert(i3_label == (i_label | ptr_label));
-  assert(ptr3_label == (i_label | ptr_label));
+  assert(i3_label == i_label || i3_label == (i_label | ptr_label));
+  assert(ptr3_label == ptr_label || ptr3_label == (i_label | ptr_label));
 #else
   assert(i3_label == i_label);
   assert(ptr3_label == ptr_label);
diff --git a/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
index 61fef1387d82a..64b3f5c812b3b 100644
--- a/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
@@ -313,6 +313,14 @@ const MemoryMapParams Linux_LoongArch64_MemoryMapParams = {
     0x100000000000, // OriginBase
 };
 
+// s390x Linux
+const MemoryMapParams Linux_S390X_MemoryMapParams = {
+    0xC00000000000, // AndMask
+    0,              // XorMask (not used)
+    0x080000000000, // ShadowBase
+    0x1C0000000000, // OriginBase
+};
+
 namespace {
 
 class DFSanABIList {
@@ -1141,6 +1149,9 @@ bool DataFlowSanitizer::initializeModule(Module &M) {
   case Triple::loongarch64:
     MapParams = &Linux_LoongArch64_MemoryMapParams;
     break;
+  case Triple::systemz:
+    MapParams = &Linux_S390X_MemoryMapParams;
+    break;
   default:
     report_fatal_error("unsupported architecture");
   }
@@ -1951,8 +1962,12 @@ Value *DataFlowSanitizer::getShadowAddress(Value *Addr,
 Value *DataFlowSanitizer::getShadowAddress(Value *Addr,
                                            BasicBlock::iterator Pos) {
   IRBuilder<> IRB(Pos->getParent(), Pos);
-  Value *ShadowOffset = getShadowOffset(Addr, IRB);
-  return getShadowAddress(Addr, Pos, ShadowOffset);
+  Value *ShadowAddr = getShadowOffset(Addr, IRB);
+  uint64_t ShadowBase = MapParams->ShadowBase;
+  if (ShadowBase != 0)
+    ShadowAddr =
+        IRB.CreateAdd(ShadowAddr, ConstantInt::get(IntptrTy, ShadowBase));
+  return getShadowAddress(Addr, Pos, ShadowAddr);
 }
 
 Value *DFSanFunction::combineShadowsThenConvert(Type *T, Value *V1, Value *V2,
@@ -2181,8 +2196,14 @@ std::pair<Value *, Value *> DFSanFunction::loadShadowFast(
       // and then the entire shadow for the second origin pointer (which will be
       // chosen by combineOrigins() iff the least-significant half of the wide
       // shadow was empty but the other half was not).
-      Value *WideShadowLo = IRB.CreateShl(
-          WideShadow, ConstantInt::get(WideShadowTy, WideShadowBitWidth / 2));
+      Value *WideShadowLo =
+          F->getParent()->getDataLayout().isLittleEndian()
+              ? IRB.CreateShl(
+                    WideShadow,
+                    ConstantInt::get(WideShadowTy, WideShadowBitWidth / 2))
+              : IRB.CreateAnd(
+                    WideShadow,
+                    ConstantInt::get(WideShadowTy, 0xFFFFFFFF00000000ULL));
       Shadows.push_back(WideShadow);
       Origins.push_back(DFS.loadNextOrigin(Pos, OriginAlign, &OriginAddr));
 

static const uptr kFlags = 0;
using AddressSpaceView = LocalAddressSpaceView;
};
#else
Copy link
Contributor

@thurstond thurstond Oct 8, 2025

Choose a reason for hiding this comment

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

Looks like the allocator and shadow constants are the tried-and-tested constants from MSan (i.e. those don't need thorough review)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's right. All memory map parameters are taken from tested MSan.

@thurstond thurstond requested a review from browneee October 8, 2025 21:33
Copy link
Contributor

@thurstond thurstond left a comment

Choose a reason for hiding this comment

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

It would be nice to land these two improvements in separate patch(es):

  • Add support for non-zero shadow base in getShadowAddress()
  • Big-endian support for wide shadows in loadShadowFast()

since they are general fixes, used for but not specific to SystemZ support.

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

Labels

clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang Clang issues not falling into any other category compiler-rt:sanitizer compiler-rt llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants