[flang-commits] [flang] [llvm] [flang-rt] - Lightweight runtime assignment function (AssignSimple) for intrinsic-type assignments (PR #213704)

Pranav Bhandarkar via flang-commits flang-commits at lists.llvm.org
Tue Aug 25 12:21:54 PDT 2026


https://github.com/bhandarkar-pranav updated https://github.com/llvm/llvm-project/pull/213704

>From 63e1249dc9d01813677d6a539cba323b22b46185 Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Sat, 1 Aug 2026 15:56:35 -0500
Subject: [PATCH 1/9] [flang-rt] - Lightweight runtime assignment function
 (AssignSimple) for intrinsic-type assignments.

This PR introduces a lightweight assignment runtime path (`_FortranAAssignSimple`) for intrinsic-type arrays
with the goal of reducing compile-time overhead seen primarily in the form of severly increased time taken by LTO.
This PR includes only the changes to the runtime (flang-rt) and as such just with this PR compile-time improvements
will not be visible.

**Problem**

When compiling Fortran code with OpenMP GPU offload and `firstprivate(allocatable_array)`, LLVM's Attributor creates excessive abstract attributes analyzing complex runtime assignment machinery:

**Symptom:**
- **Test case:** 8-element allocatable integer array with `firstprivate` clause
- **Compile time:** 24.97s (vs 0.78s for `private` - **32x slower**)
- **Root cause:** LLVM Attributor analyzing complex Fortran runtime functions

**Why this happens:**

1. `firstprivate` requires copying arrays from host to device
2. Flang generates call to `_FortranAAssign(to_device, from_host)`
3. LTO pulls in 177 runtime functions from `libflang_rt.runtime.a`
4. OpenMPOpt/Attributor analyzes all 177 functions, creating **1,041,950 abstract attributes**
5. Time spent in OpenMPOpt: **9.75s (39% of total compile time)**

**The core issue:** `_FortranAAssign` handles ALL Fortran assignment cases (scalar, array, polymorphic, character, derived type, user-defined assignment, aliasing detection, finalization) with **999 basic blocks** in a single function. For a trivial integer array copy, this forces the optimizer to analyze machinery it will never execute.

>From Attributor debug output:
```
[Attributor] Update: [AAIsDead] for ... at position {fn:_FortranAAssign}
with state Live[#BB 1/999][#TBEP 1][#KDE 0]
                    ^^^
        999 basic blocks in ONE function!
```

**Overhead:**
- **Actually executed at runtime:** ~5-10 functions, ~200 basic blocks
- **Analyzed at compile-time:** 177 functions, ~1800 basic blocks
- **Overhead:** **17x-35x more code analyzed than executed**

Intrinsic types never have dynamic components requiring deferred operations, so the `WorkQueue` in `_FortranAAssign` is not really needed.
Therefore, we split the Fortran assignment runtime API based on statically known information:

**1. `_FortranAAssignSimple` (NEW) - Fast Path**
- Handles intrinsic type arrays (integer, real, complex, logical)
- Single `memmove()` for contiguous, element-wise loop for non-contiguous
- Minimal LTO pull-in (~3-4 functions vs 177)
- Runtime checks verify correct usage

**2. `_FortranAAssign` (EXISTING) - Complex Path**
- Handles derived types, polymorphic, character, user-defined assignment
- Retains full WorkQueue, finalization, aliasing detection machinery
- Only called when actually needed

`_FortranAAssignSimple` is used when ALL conditions are true:
1. Intrinsic element type (not derived type)
2. Matching ranks (no scalar-to-array broadcasting)
3. Non-volatile
4. Not polymorphic
5. Not explicit-length character
6. Not temporary LHS

This is a part of the fix for https://github.com/llvm/llvm-project/issues/203915
---
 flang-rt/lib/runtime/assign.cpp       | 214 ++++++++++++++++++++++++++
 flang-rt/lib/runtime/tools.cpp        |   1 +
 flang-rt/unittests/Runtime/Assign.cpp | 151 ++++++++++++++++++
 flang/include/flang/Runtime/assign.h  |   4 +
 4 files changed, 370 insertions(+)

diff --git a/flang-rt/lib/runtime/assign.cpp b/flang-rt/lib/runtime/assign.cpp
index 0d0710382a055..2de590abd4316 100644
--- a/flang-rt/lib/runtime/assign.cpp
+++ b/flang-rt/lib/runtime/assign.cpp
@@ -10,6 +10,7 @@
 #include "flang-rt/runtime/assign-impl.h"
 #include "flang-rt/runtime/derived.h"
 #include "flang-rt/runtime/descriptor.h"
+#include "flang-rt/runtime/memory.h"
 #include "flang-rt/runtime/stat.h"
 #include "flang-rt/runtime/terminator.h"
 #include "flang-rt/runtime/tools.h"
@@ -851,6 +852,219 @@ void RTDEF(AssignExplicitLengthCharacter)(Descriptor &to,
           ExplicitLengthCharacterLHS);
 }
 
+void RTDEF(AssignSimple)(Descriptor &to, const Descriptor &from,
+    const char *sourceFile, int sourceLine) {
+  Terminator terminator{sourceFile, sourceLine};
+  // AssignSimple: fast path for intrinsic type assignments (integer, real,
+  // complex, logical). The compiler routes here only when:
+  //   - LHS element type is trivial (isa_trivial), not derived/polymorphic
+  //   - LHS and RHS ranks match (no scalar-to-array broadcasting)
+  //   - LHS is not volatile (volatile needs memory ordering semantics)
+
+  if (to.rank() != from.rank()) {
+    terminator.Crash("AssignSimple: rank mismatch (to.rank=%d, from.rank=%d)",
+        to.rank(), from.rank());
+  }
+  if (to.ElementBytes() != from.ElementBytes()) {
+    terminator.Crash(
+        "AssignSimple: ElementBytes mismatch (to.ElementBytes=%d, "
+        "from.ElementBytes=%d)",
+        to.ElementBytes(), from.ElementBytes());
+  }
+  if (to.type().IsDerived()) {
+    terminator.Crash("AssignSimple: Cannot assign to derived type");
+  }
+
+  std::size_t elementBytes{to.ElementBytes()};
+  std::size_t elements{from.Elements()};
+
+  // Conformability check for non-allocatable arrays.
+  // 1. For allocatable LHS, shape mismatch triggers reallocation (handled in
+  //    Step 2 below).
+  // 2. For non-allocatable LHS, shape mismatch is an error per Fortran
+  //    2018 10.2.1.2 -- the shapes must conform. This matches the
+  //    conformability check in AssignTicket::Begin().
+  //
+  // Example: x(8:1:-3) = x(5:2:-2) where x is not allocatable and LHS has 3
+  // elements, RHS has 2.
+  if (!to.IsAllocatable() && from.rank() > 0) {
+    std::size_t toElements{to.Elements()};
+    if (toElements != elements) {
+      terminator.Crash("AssignSimple: mismatching element counts in "
+                       "non-allocatable array assignment (to %zd, from %zd)",
+          toElements, elements);
+    }
+  }
+
+  // Step 1: Aliasing detection.
+  //
+  // When LHS and RHS reference overlapping memory (e.g., a(9:5:-1) = a(1:5:1)),
+  // an element-wise copy can overwrite source elements before they are read.
+  //
+  // Example of data corruption without temporary:
+  //   integer, dimension(3, 2) :: a
+  //   a = reshape((/1, 2, 3, 4, 5, 6/), (/3, 2/))
+  //   a = a(3:1:-1, 2:1:-1)  ! reverse both dimensions
+  //
+  //   The element-wise loop iterates in column-major order for both LHS and
+  //   RHS:
+  //     Iter 1: a(1,1) = a(3,2) = 6  -> overwrites a(1,1), was 1
+  //     Iter 2: a(2,1) = a(2,2) = 5  -> overwrites a(2,1), was 2
+  //     Iter 3: a(3,1) = a(1,2) = 4  -> overwrites a(3,1), was 3
+  //     Iter 4: a(1,2) = a(3,1)      -> reads 4, but expected 3 (WRONG!)
+  //     Iter 5: a(2,2) = a(2,1)      -> reads 5, but expected 2 (WRONG!)
+  //     Iter 6: a(3,2) = a(1,1)      -> reads 6, but expected 1 (WRONG!)
+  //   Result: (/6,5,4,4,5,6/) instead of (/6,5,4,3,2,1/)
+  //
+  // To fix this, we copy the RHS data into a contiguous temporary buffer
+  // before modifying the LHS. The temp preserves the original source values.
+  //
+  // MayAlias() checks whether the memory ranges described by the two
+  // descriptors overlap. It uses MaximalByteOffsetRange() to compute the
+  // byte extent of each descriptor (accounting for negative strides), then
+  // checks if those ranges overlap via RangesOverlap(). All three are static
+  // functions in this file -- calling them from AssignSimple adds zero
+  // additional LTO pull-in.
+  //
+  // When aliasing is detected, we always create a temporary, even if both
+  // sides are contiguous (where memmove would handle overlap correctly).
+  // This keeps the code simple and covers the case where an allocatable LHS
+  // needs reallocation: deallocating the LHS would free the RHS's backing
+  // memory if they alias.
+  //   Example: integer, allocatable :: a(:)
+  //            allocate(a(5)); a = [1,2,3,4,5]
+  //            a = a(1:3)  ! shapes differ -> deallocate a -> frees a(1:3)'s
+  //            data
+  //
+  // TODO: Refining the condition for creating a temporary buffer.
+  // For better performance on contiguous aliased assignments that do not
+  // require reallocation, we could refine the condition to only create a temp
+  // when:
+  // (needsReallocation || !to.IsContiguous() || !from.IsContiguous())
+  // where
+  // needsReallocation = (to.isAllocatable() && (!to.isAllocated ||
+  //                                             shape_mismatch))
+  // For needsReallocation, see Step 2 below.
+  // Right now though, the simpler approach of always creating a temporary
+  // when aliasing is detected is fine.
+  //
+  // The temporary buffer is allocated via AllocateMemoryOrCrash(), which is
+  // a thin wrapper around std::malloc. This is GPU-safe: both
+  // AllocateMemoryOrCrash and std::malloc are available in GPU device code
+  // (via the device-side heap allocator), and Assign already calls
+  // Descriptor::Allocate() which goes through the same std::malloc path.
+  char *tempBuffer{nullptr};
+  if (MayAlias(to, from)) {
+    std::size_t totalBytes{elements * elementBytes};
+    tempBuffer =
+        static_cast<char *>(AllocateMemoryOrCrash(terminator, totalBytes));
+    // Copy from's data into the contiguous temp buffer, element by element.
+    // This handles non-contiguous RHS (e.g., strided slices) by walking
+    // the descriptor's subscripts.
+    if (from.IsContiguous()) {
+      runtime::memcpy(tempBuffer, from.OffsetElement(), totalBytes);
+    } else {
+      SubscriptValue fromAt[maxRank];
+      from.GetLowerBounds(fromAt);
+      char *tempAt{tempBuffer};
+      for (std::size_t n{elements}; n-- > 0;
+           from.IncrementSubscripts(fromAt), tempAt += elementBytes) {
+        runtime::memcpy(tempAt, from.Element<const char>(fromAt), elementBytes);
+      }
+    }
+  }
+
+  // Step 2: Handle allocation/reallocation for allocatable LHS.
+  //
+  // This must come AFTER the aliasing check above. That is because if we
+  // must call deallocate the LHS (If LHS and RHS alias and shapes differ), the
+  // Deallocate() call below would free the memory that the RHS points to. The
+  // temporary created in Step 1 preserves the RHS data, making the deallocation
+  // safe.
+  //
+  // Per Fortran 2018 10.2.1.3(3): for allocatable LHS, if the LHS is
+  // already allocated and shapes differ, it must be deallocated and
+  // reallocated to match the RHS shape.
+  if (to.IsAllocatable()) {
+    bool needsReallocation{false};
+
+    if (!to.IsAllocated()) {
+      needsReallocation = true;
+    } else if (from.rank() > 0) {
+      int rank{to.rank()};
+      for (int j{0}; j < rank; ++j) {
+        if (to.GetDimension(j).Extent() != from.GetDimension(j).Extent()) {
+          needsReallocation = true;
+          break;
+        }
+      }
+    }
+
+    if (needsReallocation) {
+      if (to.IsAllocated()) {
+        to.Deallocate();
+      }
+      to.raw().elem_len = elementBytes;
+      int rank{to.rank()};
+      auto stride{static_cast<SubscriptValue>(elementBytes)};
+      for (int j{0}; j < rank; ++j) {
+        const auto &fromDim{from.GetDimension(j)};
+        auto &toDim{to.GetDimension(j)};
+        toDim.SetBounds(fromDim.LowerBound(), fromDim.UpperBound());
+        toDim.SetByteStride(stride);
+        stride *= toDim.Extent();
+      }
+      int stat{to.Allocate(kNoAsyncObject)};
+      if (stat != StatOk) {
+        terminator.Crash("AssignSimple: allocation failed (stat=%d)", stat);
+      }
+    }
+  }
+
+  // Step 3: Copy data into LHS.
+  //
+  // If we created a temporary in Step 1 (aliasing detected), copy from
+  // the contiguous temp buffer. Otherwise, copy directly from the RHS.
+  if (tempBuffer) {
+    // Source is the contiguous temp buffer. Destination (LHS) may or may
+    // not be contiguous.
+    if (to.IsContiguous()) {
+      // Both temp (always contiguous) and LHS are contiguous: bulk copy.
+      runtime::memcpy(to.OffsetElement(), tempBuffer, elements * elementBytes);
+    } else {
+      // LHS is non-contiguous (e.g., strided section): element-wise copy
+      // from the contiguous temp buffer into LHS's strided layout.
+      SubscriptValue toAt[maxRank];
+      to.GetLowerBounds(toAt);
+      const char *tempAt{tempBuffer};
+      for (std::size_t n{elements}; n-- > 0;
+           to.IncrementSubscripts(toAt), tempAt += elementBytes) {
+        runtime::memcpy(to.Element<char>(toAt), tempAt, elementBytes);
+      }
+    }
+    FreeMemory(tempBuffer);
+  } else {
+    // No aliasing: copy directly from RHS to LHS.
+    if (to.IsContiguous() && from.IsContiguous()) {
+      // Both contiguous: memmove handles any incidental overlap safely.
+      runtime::memmove(
+          to.OffsetElement(), from.OffsetElement(), elements * elementBytes);
+    } else {
+      // At least one non-contiguous: element-wise copy.
+      // This handles strided slices, transformational intrinsic results, etc.
+      SubscriptValue toAt[maxRank];
+      to.GetLowerBounds(toAt);
+      SubscriptValue fromAt[maxRank];
+      from.GetLowerBounds(fromAt);
+      for (std::size_t n{elements}; n-- > 0;
+           to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) {
+        runtime::memmove(to.Element<char>(toAt),
+            from.Element<const char>(fromAt), elementBytes);
+      }
+    }
+  }
+}
+
 void RTDEF(AssignPolymorphic)(Descriptor &to, const Descriptor &from,
     const char *sourceFile, int sourceLine) {
   Terminator terminator{sourceFile, sourceLine};
diff --git a/flang-rt/lib/runtime/tools.cpp b/flang-rt/lib/runtime/tools.cpp
index 361e2469b4a89..b7408c81f83f4 100644
--- a/flang-rt/lib/runtime/tools.cpp
+++ b/flang-rt/lib/runtime/tools.cpp
@@ -205,6 +205,7 @@ RT_API_ATTRS void ShallowCopyInner(const Descriptor &to, const Descriptor &from,
 template <typename P>
 RT_API_ATTRS void ShallowCopyRank(const Descriptor &to, const Descriptor &from,
     bool toIsContiguous, bool fromIsContiguous) {
+  INTERNAL_CHECK(to.rank() == from.rank());
   // Specialize only common low ranks; use generic fallback for higher ranks
   switch (to.rank()) {
   case 1:
diff --git a/flang-rt/unittests/Runtime/Assign.cpp b/flang-rt/unittests/Runtime/Assign.cpp
index 4001cc90ca0a1..b1d017a784538 100644
--- a/flang-rt/unittests/Runtime/Assign.cpp
+++ b/flang-rt/unittests/Runtime/Assign.cpp
@@ -53,3 +53,154 @@ TEST(Assign, RTNAME(CopyInAssign)) {
 
   intResultStrided.Destroy();
 }
+
+TEST(AssignSimple, AliasedReverseStride) {
+  // Test aliasing detection with reverse-stride copy: a(5:1:-1) = a(1:5)
+  // This exercises the MayAlias() detection and temporary buffer path.
+  // Without temp buffer, the element-wise copy would corrupt data by
+  // overwriting source elements before they're read.
+
+  // Create backing storage as a C++ array
+  int data[5] = {1, 2, 3, 4, 5};
+  constexpr int elementBytes = sizeof(int);
+  TypeCode intType{TypeCategory::Integer, 4};
+
+  // Create source descriptor: forward view (1:5)
+  StaticDescriptor<1> staticSource;
+  Descriptor &source{staticSource.descriptor()};
+  SubscriptValue extent[1]{5};
+  source.Establish(intType, elementBytes, data, 1, extent);
+  source.GetDimension(0).SetLowerBound(1);
+
+  // Create dest descriptor: reverse view (5:1:-1) of same memory
+  StaticDescriptor<1> staticDest;
+  Descriptor &dest{staticDest.descriptor()};
+  dest.Establish(
+      intType, elementBytes, &data[4], 1, extent); // Start at last element
+  dest.GetDimension(0).SetLowerBound(1);
+  dest.GetDimension(0).SetByteStride(-elementBytes); // Negative stride
+
+  RTNAME(AssignSimple)(dest, source, __FILE__, __LINE__);
+
+  // Verify reverse copy succeeded.
+  // The backing array should now be [5,4,3,2,1] (reversed from [1,2,3,4,5])
+  int expected[5] = {5, 4, 3, 2, 1};
+  EXPECT_EQ(std::memcmp(data, expected, 5 * sizeof(int)), 0);
+}
+
+TEST(AssignSimple, ReallocateUnallocated) {
+  // Test allocatable reallocation from unallocated state
+  StaticDescriptor<1> staticDest;
+  Descriptor &dest{staticDest.descriptor()};
+  dest.Establish(TypeCode{TypeCategory::Integer, 4}, sizeof(int), nullptr, 1,
+      nullptr, CFI_attribute_allocatable);
+  dest.GetDimension(0).SetBounds(1, 0);
+  // dest is now unallocated
+
+  auto source{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{4}, std::vector<int>{10, 20, 30, 40}, sizeof(int))};
+
+  EXPECT_FALSE(dest.IsAllocated());
+
+  RTNAME(AssignSimple)(dest, *source, __FILE__, __LINE__);
+
+  // Verify dest is now allocated with correct shape and data
+  EXPECT_TRUE(dest.IsAllocated());
+  EXPECT_EQ(dest.rank(), 1);
+  EXPECT_EQ(dest.GetDimension(0).LowerBound(), 1);
+  EXPECT_EQ(dest.GetDimension(0).Extent(), 4);
+  EXPECT_EQ(dest.Elements(), 4);
+
+  int expected[4] = {10, 20, 30, 40};
+  EXPECT_EQ(
+      std::memcmp(dest.OffsetElement<int>(0), expected, 4 * sizeof(int)), 0);
+
+  // Verify source unchanged
+  EXPECT_EQ(
+      std::memcmp(source->OffsetElement<int>(0), expected, 4 * sizeof(int)), 0);
+
+  dest.Destroy();
+  source->Destroy();
+}
+
+TEST(AssignSimple, ReallocateShapeMismatch) {
+  // Test allocatable reallocation when shape (extent) differs
+  auto dest{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{3}, std::vector<int>{1, 2, 3}, sizeof(int))};
+
+  auto source{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{5}, std::vector<int>{10, 20, 30, 40, 50}, sizeof(int))};
+
+  EXPECT_TRUE(dest->IsAllocated());
+  EXPECT_EQ(dest->GetDimension(0).Extent(), 3);
+
+  RTNAME(AssignSimple)(*dest, *source, __FILE__, __LINE__);
+
+  // Verify dest was reallocated with new extent matching source
+  EXPECT_TRUE(dest->IsAllocated());
+  EXPECT_EQ(dest->rank(), 1);
+  EXPECT_EQ(dest->GetDimension(0).LowerBound(), 1);
+  EXPECT_EQ(dest->GetDimension(0).Extent(), 5);
+  EXPECT_EQ(dest->Elements(), 5);
+
+  int expected[5] = {10, 20, 30, 40, 50};
+  EXPECT_EQ(
+      std::memcmp(dest->OffsetElement<int>(0), expected, 5 * sizeof(int)), 0);
+
+  // Verify source unchanged
+  EXPECT_EQ(
+      std::memcmp(source->OffsetElement<int>(0), expected, 5 * sizeof(int)), 0);
+
+  dest->Destroy();
+  source->Destroy();
+}
+
+TEST(AssignSimple, NonContiguousToContiguous) {
+  // Test non-contiguous source (strided) to contiguous destination
+  // Pattern: take every other element from an 8-element array
+  auto source{MakeArray<TypeCategory::Integer, 4>(std::vector<int>{8},
+      std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8}, sizeof(int))};
+
+  // Make source non-contiguous: stride=2*sizeof(int), extent=4
+  // This gives us elements [1, 3, 5, 7] from the backing array
+  source->GetDimension(0).SetByteStride(sizeof(int) * 2);
+  source->GetDimension(0).SetExtent(4);
+  EXPECT_FALSE(source->IsContiguous());
+
+  auto dest{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{4}, std::vector<int>{0, 0, 0, 0}, sizeof(int))};
+  EXPECT_TRUE(dest->IsContiguous());
+
+  RTNAME(AssignSimple)(*dest, *source, __FILE__, __LINE__);
+
+  // Verify dest has strided elements from source
+  int expected[4] = {1, 3, 5, 7};
+  EXPECT_EQ(
+      std::memcmp(dest->OffsetElement<int>(0), expected, 4 * sizeof(int)), 0);
+  EXPECT_TRUE(dest->IsContiguous());
+
+  dest->Destroy();
+  source->Destroy();
+}
+
+TEST(AssignSimple, ZeroSizeArray) {
+  // Test zero-size array edge case
+  auto source{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{0}, std::vector<int>{}, sizeof(int))};
+
+  auto dest{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{0}, std::vector<int>{}, sizeof(int))};
+
+  EXPECT_EQ(source->Elements(), 0);
+  EXPECT_EQ(dest->Elements(), 0);
+
+  // Should not crash with zero-size arrays
+  RTNAME(AssignSimple)(*dest, *source, __FILE__, __LINE__);
+
+  // Verify both still have 0 elements
+  EXPECT_EQ(dest->Elements(), 0);
+  EXPECT_EQ(source->Elements(), 0);
+
+  dest->Destroy();
+  source->Destroy();
+}
diff --git a/flang/include/flang/Runtime/assign.h b/flang/include/flang/Runtime/assign.h
index c145239c8c1ad..bf050ff890068 100644
--- a/flang/include/flang/Runtime/assign.h
+++ b/flang/include/flang/Runtime/assign.h
@@ -79,6 +79,10 @@ void RTDECL(AssignExplicitLengthCharacter)(Descriptor &to,
 // This variant is assignments to whole polymorphic allocatables.
 void RTDECL(AssignPolymorphic)(Descriptor &to, const Descriptor &from,
     const char *sourceFile = nullptr, int sourceLine = 0);
+// Fast path for simple intrinsic type assignments (no derived types, no
+// finalization)
+void RTDECL(AssignSimple)(Descriptor &to, const Descriptor &from,
+    const char *sourceFile = nullptr, int sourceLine = 0);
 } // extern "C"
 } // namespace Fortran::runtime
 #endif // FORTRAN_RUNTIME_ASSIGN_H_

>From d7561f51e330c1177723b1bc01f029fbd54cc549 Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Mon, 3 Aug 2026 11:01:05 -0500
Subject: [PATCH 2/9] Fix formatting issues

---
 flang-rt/lib/runtime/assign.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/flang-rt/lib/runtime/assign.cpp b/flang-rt/lib/runtime/assign.cpp
index 2de590abd4316..dd04ac15ba93f 100644
--- a/flang-rt/lib/runtime/assign.cpp
+++ b/flang-rt/lib/runtime/assign.cpp
@@ -866,9 +866,8 @@ void RTDEF(AssignSimple)(Descriptor &to, const Descriptor &from,
         to.rank(), from.rank());
   }
   if (to.ElementBytes() != from.ElementBytes()) {
-    terminator.Crash(
-        "AssignSimple: ElementBytes mismatch (to.ElementBytes=%d, "
-        "from.ElementBytes=%d)",
+    terminator.Crash("AssignSimple: ElementBytes mismatch (to.ElementBytes=%d, "
+                     "from.ElementBytes=%d)",
         to.ElementBytes(), from.ElementBytes());
   }
   if (to.type().IsDerived()) {

>From 309a28d095e02a5c41ea1995ae9d0e305062ad2b Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Mon, 3 Aug 2026 11:07:24 -0500
Subject: [PATCH 3/9] more formatting fixes

---
 flang-rt/lib/runtime/assign.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/flang-rt/lib/runtime/assign.cpp b/flang-rt/lib/runtime/assign.cpp
index dd04ac15ba93f..d3d464c2eb9d5 100644
--- a/flang-rt/lib/runtime/assign.cpp
+++ b/flang-rt/lib/runtime/assign.cpp
@@ -967,7 +967,7 @@ void RTDEF(AssignSimple)(Descriptor &to, const Descriptor &from,
       from.GetLowerBounds(fromAt);
       char *tempAt{tempBuffer};
       for (std::size_t n{elements}; n-- > 0;
-           from.IncrementSubscripts(fromAt), tempAt += elementBytes) {
+          from.IncrementSubscripts(fromAt), tempAt += elementBytes) {
         runtime::memcpy(tempAt, from.Element<const char>(fromAt), elementBytes);
       }
     }
@@ -1037,7 +1037,7 @@ void RTDEF(AssignSimple)(Descriptor &to, const Descriptor &from,
       to.GetLowerBounds(toAt);
       const char *tempAt{tempBuffer};
       for (std::size_t n{elements}; n-- > 0;
-           to.IncrementSubscripts(toAt), tempAt += elementBytes) {
+          to.IncrementSubscripts(toAt), tempAt += elementBytes) {
         runtime::memcpy(to.Element<char>(toAt), tempAt, elementBytes);
       }
     }
@@ -1056,7 +1056,7 @@ void RTDEF(AssignSimple)(Descriptor &to, const Descriptor &from,
       SubscriptValue fromAt[maxRank];
       from.GetLowerBounds(fromAt);
       for (std::size_t n{elements}; n-- > 0;
-           to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) {
+          to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) {
         runtime::memmove(to.Element<char>(toAt),
             from.Element<const char>(fromAt), elementBytes);
       }

>From d54fa96ae9b1edab9eb703d3190b0408404178cd Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Wed, 5 Aug 2026 22:37:45 -0500
Subject: [PATCH 4/9] Address review comments from tblah and mjklemm

---
 flang-rt/lib/runtime/assign.cpp | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/flang-rt/lib/runtime/assign.cpp b/flang-rt/lib/runtime/assign.cpp
index d3d464c2eb9d5..2da497fab3881 100644
--- a/flang-rt/lib/runtime/assign.cpp
+++ b/flang-rt/lib/runtime/assign.cpp
@@ -866,10 +866,15 @@ void RTDEF(AssignSimple)(Descriptor &to, const Descriptor &from,
         to.rank(), from.rank());
   }
   if (to.ElementBytes() != from.ElementBytes()) {
-    terminator.Crash("AssignSimple: ElementBytes mismatch (to.ElementBytes=%d, "
-                     "from.ElementBytes=%d)",
+    terminator.Crash("AssignSimple: ElementBytes mismatch (to.ElementBytes=%zu, "
+                     "from.ElementBytes=%zu)",
         to.ElementBytes(), from.ElementBytes());
   }
+  if (to.type() != from.type()) {
+    terminator.Crash(
+        "AssignSimple: mismatching types (to code %d != from code %d)",
+        to.type().raw(), from.type().raw());
+  }
   if (to.type().IsDerived()) {
     terminator.Crash("AssignSimple: Cannot assign to derived type");
   }

>From 9625fd2f464efc27fa900bb3133e92f464b8c7d7 Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Tue, 18 Aug 2026 12:41:06 -0500
Subject: [PATCH 5/9] fix clang-format issues and flang-rt unittest for
 Assign.cpp

---
 flang-rt/lib/runtime/assign.cpp       |  5 +++--
 flang-rt/unittests/Runtime/Assign.cpp | 12 ++++++------
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/flang-rt/lib/runtime/assign.cpp b/flang-rt/lib/runtime/assign.cpp
index 2da497fab3881..1182ddb77e8e7 100644
--- a/flang-rt/lib/runtime/assign.cpp
+++ b/flang-rt/lib/runtime/assign.cpp
@@ -866,8 +866,9 @@ void RTDEF(AssignSimple)(Descriptor &to, const Descriptor &from,
         to.rank(), from.rank());
   }
   if (to.ElementBytes() != from.ElementBytes()) {
-    terminator.Crash("AssignSimple: ElementBytes mismatch (to.ElementBytes=%zu, "
-                     "from.ElementBytes=%zu)",
+    terminator.Crash(
+        "AssignSimple: ElementBytes mismatch (to.ElementBytes=%zu, "
+        "from.ElementBytes=%zu)",
         to.ElementBytes(), from.ElementBytes());
   }
   if (to.type() != from.type()) {
diff --git a/flang-rt/unittests/Runtime/Assign.cpp b/flang-rt/unittests/Runtime/Assign.cpp
index b1d017a784538..a3a1637614a53 100644
--- a/flang-rt/unittests/Runtime/Assign.cpp
+++ b/flang-rt/unittests/Runtime/Assign.cpp
@@ -109,7 +109,7 @@ TEST(AssignSimple, ReallocateUnallocated) {
   EXPECT_EQ(dest.rank(), 1);
   EXPECT_EQ(dest.GetDimension(0).LowerBound(), 1);
   EXPECT_EQ(dest.GetDimension(0).Extent(), 4);
-  EXPECT_EQ(dest.Elements(), 4);
+  EXPECT_EQ(dest.Elements(), 4U);
 
   int expected[4] = {10, 20, 30, 40};
   EXPECT_EQ(
@@ -141,7 +141,7 @@ TEST(AssignSimple, ReallocateShapeMismatch) {
   EXPECT_EQ(dest->rank(), 1);
   EXPECT_EQ(dest->GetDimension(0).LowerBound(), 1);
   EXPECT_EQ(dest->GetDimension(0).Extent(), 5);
-  EXPECT_EQ(dest->Elements(), 5);
+  EXPECT_EQ(dest->Elements(), 5U);
 
   int expected[5] = {10, 20, 30, 40, 50};
   EXPECT_EQ(
@@ -191,15 +191,15 @@ TEST(AssignSimple, ZeroSizeArray) {
   auto dest{MakeArray<TypeCategory::Integer, 4>(
       std::vector<int>{0}, std::vector<int>{}, sizeof(int))};
 
-  EXPECT_EQ(source->Elements(), 0);
-  EXPECT_EQ(dest->Elements(), 0);
+  EXPECT_EQ(source->Elements(), 0U);
+  EXPECT_EQ(dest->Elements(), 0U);
 
   // Should not crash with zero-size arrays
   RTNAME(AssignSimple)(*dest, *source, __FILE__, __LINE__);
 
   // Verify both still have 0 elements
-  EXPECT_EQ(dest->Elements(), 0);
-  EXPECT_EQ(source->Elements(), 0);
+  EXPECT_EQ(dest->Elements(), 0U);
+  EXPECT_EQ(source->Elements(), 0U);
 
   dest->Destroy();
   source->Destroy();

>From 425fad2429d698919944727cb8bb032f1f3a887b Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Wed, 12 Aug 2026 14:14:51 -0500
Subject: [PATCH 6/9] Add more tests per Michael Klemms request

---
 flang-rt/unittests/Runtime/Assign.cpp | 164 ++++++++++++++++++++++++++
 1 file changed, 164 insertions(+)

diff --git a/flang-rt/unittests/Runtime/Assign.cpp b/flang-rt/unittests/Runtime/Assign.cpp
index a3a1637614a53..4f433099313cb 100644
--- a/flang-rt/unittests/Runtime/Assign.cpp
+++ b/flang-rt/unittests/Runtime/Assign.cpp
@@ -204,3 +204,167 @@ TEST(AssignSimple, ZeroSizeArray) {
   dest->Destroy();
   source->Destroy();
 }
+
+TEST(AssignSimple, AliasedOverlappingSection) {
+  // Test aliasing with overlapping array sections: a(3:7) = a(1:5)
+  // This is a classic case where the destination partially overlaps the source.
+  // Without a temporary buffer, elements would be corrupted as the copy progresses.
+  //
+  // Example:
+  // Initial:  [1, 2, 3, 4, 5, 6, 7, 8]
+  // a(3:7) = a(1:5) should produce [1, 2, 1, 2, 3, 4, 5, 8]
+
+  int data[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+  constexpr int elementBytes = sizeof(int);
+  TypeCode intType{TypeCategory::Integer, 4};
+
+  // Source descriptor: a(1:5) - elements at indices 0-4
+  StaticDescriptor<1> staticSource;
+  Descriptor &source{staticSource.descriptor()};
+  SubscriptValue extent[1]{5};
+  source.Establish(intType, elementBytes, data, 1, extent);
+  source.GetDimension(0).SetLowerBound(1);
+
+  // Dest descriptor: a(3:7) - elements at indices 2-6 (same backing array)
+  StaticDescriptor<1> staticDest;
+  Descriptor &dest{staticDest.descriptor()};
+  dest.Establish(intType, elementBytes, &data[2], 1, extent);
+  dest.GetDimension(0).SetLowerBound(1);
+
+  RTNAME(AssignSimple)(dest, source, __FILE__, __LINE__);
+
+  // Expected result: [1, 2, 1, 2, 3, 4, 5, 8]
+  // Positions 3-7 (indices 2-6) should now contain values from positions 1-5
+  int expected[8] = {1, 2, 1, 2, 3, 4, 5, 8};
+  EXPECT_EQ(std::memcmp(data, expected, 8 * sizeof(int)), 0);
+}
+
+TEST(AssignSimple, AliasedTwoDimensionalReverse) {
+  // Test aliasing in 2D array with column reversal: a(:, 2:1:-1) = a(:, 1:2)
+  // This tests that aliasing detection works across multiple dimensions.
+  //
+  // Initial array (3x2, column-major):
+  //   Column 1  Column 2
+  //   [1]       [4]
+  //   [2]       [5]
+  //   [3]       [6]
+  //
+  // After a(:, 2:1:-1) = a(:, 1:2), should be:
+  //   [4]  [1]
+  //   [5]  [2]
+  //   [6]  [3]
+  //
+  // Backing storage (column-major): [1,2,3,4,5,6] -> [4,5,6,1,2,3]
+
+  int data[6] = {1, 2, 3, 4, 5, 6};
+  constexpr int elementBytes = sizeof(int);
+  TypeCode intType{TypeCategory::Integer, 4};
+
+  // Source descriptor: a(:, 1:2) - all rows, columns 1-2 (forward)
+  StaticDescriptor<2> staticSource;
+  Descriptor &source{staticSource.descriptor()};
+  SubscriptValue extent[2]{3, 2}; // 3 rows, 2 columns
+  source.Establish(intType, elementBytes, data, 2, extent);
+  source.GetDimension(0).SetLowerBound(1);
+  source.GetDimension(0).SetByteStride(elementBytes); // Rows are contiguous
+  source.GetDimension(1).SetLowerBound(1);
+  source.GetDimension(1).SetByteStride(3 * elementBytes); // Column stride
+
+  // Dest descriptor: a(:, 2:1:-1) - all rows, columns 2-1 (reverse)
+  StaticDescriptor<2> staticDest;
+  Descriptor &dest{staticDest.descriptor()};
+  dest.Establish(
+      intType, elementBytes, &data[3], 2, extent); // Start at column 2
+  dest.GetDimension(0).SetLowerBound(1);
+  dest.GetDimension(0).SetByteStride(elementBytes);
+  dest.GetDimension(1).SetLowerBound(1);
+  dest.GetDimension(1).SetByteStride(-3 * elementBytes); // Negative stride
+
+  RTNAME(AssignSimple)(dest, source, __FILE__, __LINE__);
+
+  // Expected: columns swapped
+  // Column-major storage: [4,5,6,1,2,3]
+  int expected[6] = {4, 5, 6, 1, 2, 3};
+  EXPECT_EQ(std::memcmp(data, expected, 6 * sizeof(int)), 0);
+}
+
+TEST(AssignSimple, AliasedReallocatableSelfAssign) {
+  // Test aliasing when LHS is allocatable and gets reallocated during a
+  // self-assignment with a different shape: a = a(1:3)
+  //
+  // This is tricky because:
+  // 1. Aliasing is detected (LHS and RHS point to same memory)
+  // 2. Shapes differ, so reallocation is needed
+  // 3. Deallocating LHS would free RHS memory
+  // 4. Temp buffer must be created BEFORE deallocation
+
+  // Initial array: [10, 20, 30, 40, 50]
+  auto dest{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{5}, std::vector<int>{10, 20, 30, 40, 50}, sizeof(int))};
+
+  // Create source descriptor pointing to first 3 elements of dest
+  StaticDescriptor<1> staticSource;
+  Descriptor &source{staticSource.descriptor()};
+  SubscriptValue extent[1]{3};
+  source.Establish(TypeCode{TypeCategory::Integer, 4}, sizeof(int),
+      dest->OffsetElement(), 1, extent);
+  source.GetDimension(0).SetLowerBound(1);
+
+  EXPECT_TRUE(dest->IsAllocated());
+  EXPECT_EQ(dest->GetDimension(0).Extent(), 5);
+
+  // Self-assign with different shape: dest = dest(1:3)
+  RTNAME(AssignSimple)(*dest, source, __FILE__, __LINE__);
+
+  // Verify dest was reallocated to size 3 with correct values
+  EXPECT_TRUE(dest->IsAllocated());
+  EXPECT_EQ(dest->GetDimension(0).Extent(), 3);
+
+  int expected[3] = {10, 20, 30};
+  EXPECT_EQ(
+      std::memcmp(dest->OffsetElement<int>(0), expected, 3 * sizeof(int)), 0);
+
+  dest->Destroy();
+}
+
+TEST(AssignSimple, AliasedNonContiguousToNonContiguous) {
+  // Test aliasing where both LHS and RHS are non-contiguous strided views
+  // a(6:2:-2) = a(1:5:2)
+  //
+  // This ensures the temporary buffer path works correctly when BOTH sides
+  // are non-contiguous, requiring element-wise copy in both directions.
+  //
+  // Initial: [1, 2, 3, 4, 5, 6, 7, 8]
+  // Source: a(1:5:2) = indices [0, 2, 4] = [1, 3, 5]
+  // Dest: a(6:2:-2) = indices [5, 3, 1] = [6, 4, 2] (reverse)
+  //
+  // After assignment: [1, 5, 3, 3, 5, 1, 7, 8]
+
+  int data[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+  constexpr int elementBytes = sizeof(int);
+  TypeCode intType{TypeCategory::Integer, 4};
+
+  // Source: a(1:5:2) - indices [0, 2, 4] forward, stride 2
+  StaticDescriptor<1> staticSource;
+  Descriptor &source{staticSource.descriptor()};
+  SubscriptValue extent[1]{3};
+  source.Establish(intType, elementBytes, &data[0], 1, extent);
+  source.GetDimension(0).SetLowerBound(1);
+  source.GetDimension(0).SetByteStride(2 * elementBytes);
+  EXPECT_FALSE(source.IsContiguous());
+
+  // Dest: a(6:2:-2) - indices [5, 3, 1] reverse, stride -2
+  StaticDescriptor<1> staticDest;
+  Descriptor &dest{staticDest.descriptor()};
+  dest.Establish(intType, elementBytes, &data[5], 1, extent); // Start at index 5
+  dest.GetDimension(0).SetLowerBound(1);
+  dest.GetDimension(0).SetByteStride(-2 * elementBytes);
+  EXPECT_FALSE(dest.IsContiguous());
+
+  RTNAME(AssignSimple)(dest, source, __FILE__, __LINE__);
+
+  // Expected: dest positions [5,3,1] get source values [1,3,5]
+  // Result: [1, 5, 3, 3, 5, 1, 7, 8]
+  int expected[8] = {1, 5, 3, 3, 5, 1, 7, 8};
+  EXPECT_EQ(std::memcmp(data, expected, 8 * sizeof(int)), 0);
+}

>From 4762026fa7659737f91f137d3c7163bf179a655c Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Wed, 19 Aug 2026 18:04:02 -0500
Subject: [PATCH 7/9] fix clang-format issues

---
 flang-rt/unittests/Runtime/Assign.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/flang-rt/unittests/Runtime/Assign.cpp b/flang-rt/unittests/Runtime/Assign.cpp
index 4f433099313cb..a7dc99a12a8a0 100644
--- a/flang-rt/unittests/Runtime/Assign.cpp
+++ b/flang-rt/unittests/Runtime/Assign.cpp
@@ -208,7 +208,8 @@ TEST(AssignSimple, ZeroSizeArray) {
 TEST(AssignSimple, AliasedOverlappingSection) {
   // Test aliasing with overlapping array sections: a(3:7) = a(1:5)
   // This is a classic case where the destination partially overlaps the source.
-  // Without a temporary buffer, elements would be corrupted as the copy progresses.
+  // Without a temporary buffer, elements would be corrupted as the copy
+  // progresses.
   //
   // Example:
   // Initial:  [1, 2, 3, 4, 5, 6, 7, 8]
@@ -356,7 +357,8 @@ TEST(AssignSimple, AliasedNonContiguousToNonContiguous) {
   // Dest: a(6:2:-2) - indices [5, 3, 1] reverse, stride -2
   StaticDescriptor<1> staticDest;
   Descriptor &dest{staticDest.descriptor()};
-  dest.Establish(intType, elementBytes, &data[5], 1, extent); // Start at index 5
+  dest.Establish(
+      intType, elementBytes, &data[5], 1, extent); // Start at index 5
   dest.GetDimension(0).SetLowerBound(1);
   dest.GetDimension(0).SetByteStride(-2 * elementBytes);
   EXPECT_FALSE(dest.IsContiguous());

>From f6c7be4b5301e5398157947514e975fa81867f77 Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Wed, 19 Aug 2026 19:26:54 -0500
Subject: [PATCH 8/9] Reject CHARACTER types in AssignSimple and add death
 tests

AssignSimple is only intended for trivial intrinsic types (integer, real,
complex, logical). Add a guard that crashes on CHARACTER type input,
matching the existing derived-type guard.

Add death tests covering all five crash paths in AssignSimple: rank
mismatch, element-bytes mismatch, derived type, character type, and
non-allocatable element count mismatch.

Co-Authored-By: Claude Opus 4 (1M context) <noreply at anthropic.com>
---
 flang-rt/lib/runtime/assign.cpp       |  3 ++
 flang-rt/unittests/Runtime/Assign.cpp | 77 +++++++++++++++++++++++++++
 2 files changed, 80 insertions(+)

diff --git a/flang-rt/lib/runtime/assign.cpp b/flang-rt/lib/runtime/assign.cpp
index 1182ddb77e8e7..2fc584d2569e7 100644
--- a/flang-rt/lib/runtime/assign.cpp
+++ b/flang-rt/lib/runtime/assign.cpp
@@ -879,6 +879,9 @@ void RTDEF(AssignSimple)(Descriptor &to, const Descriptor &from,
   if (to.type().IsDerived()) {
     terminator.Crash("AssignSimple: Cannot assign to derived type");
   }
+  if (to.type().IsCharacter()) {
+    terminator.Crash("AssignSimple: Cannot assign to character type");
+  }
 
   std::size_t elementBytes{to.ElementBytes()};
   std::size_t elements{from.Elements()};
diff --git a/flang-rt/unittests/Runtime/Assign.cpp b/flang-rt/unittests/Runtime/Assign.cpp
index a7dc99a12a8a0..089736c84a029 100644
--- a/flang-rt/unittests/Runtime/Assign.cpp
+++ b/flang-rt/unittests/Runtime/Assign.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "flang/Runtime/assign.h"
+#include "CrashHandlerFixture.h"
 #include "tools.h"
 #include "gtest/gtest.h"
 #include <vector>
@@ -370,3 +371,79 @@ TEST(AssignSimple, AliasedNonContiguousToNonContiguous) {
   int expected[8] = {1, 5, 3, 3, 5, 1, 7, 8};
   EXPECT_EQ(std::memcmp(data, expected, 8 * sizeof(int)), 0);
 }
+
+//------------------------------------------------------------------------------
+// Death tests for AssignSimple: verify that invalid inputs crash as expected.
+//------------------------------------------------------------------------------
+struct AssignSimpleCrash : CrashHandlerFixture {};
+
+TEST(AssignSimpleCrash, RankMismatch) {
+  auto dest{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{3}, std::vector<int>{1, 2, 3}, sizeof(int))};
+  auto source{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{2, 2}, std::vector<int>{1, 2, 3, 4}, sizeof(int))};
+  ASSERT_DEATH(RTNAME(AssignSimple)(*dest, *source, __FILE__, __LINE__),
+      "AssignSimple: rank mismatch");
+}
+
+TEST(AssignSimpleCrash, ElementBytesMismatch) {
+  // 4-byte integers vs 8-byte integers
+  auto dest{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{3}, std::vector<int>{1, 2, 3}, sizeof(int))};
+  auto source{MakeArray<TypeCategory::Integer, 8>(
+      std::vector<int>{3}, std::vector<std::int64_t>{1, 2, 3},
+      sizeof(std::int64_t))};
+  ASSERT_DEATH(RTNAME(AssignSimple)(*dest, *source, __FILE__, __LINE__),
+      "AssignSimple: ElementBytes mismatch");
+}
+
+TEST(AssignSimpleCrash, DerivedType) {
+  TypeCode structType{static_cast<Fortran::ISO::CFI_type_t>(CFI_type_struct)};
+  SubscriptValue extent[1]{2};
+  int destData[2] = {1, 2};
+  int srcData[2] = {3, 4};
+
+  StaticDescriptor<1> staticDest;
+  Descriptor &dest{staticDest.descriptor()};
+  dest.Establish(structType, sizeof(int), destData, 1, extent);
+  dest.GetDimension(0).SetLowerBound(1);
+
+  StaticDescriptor<1> staticSource;
+  Descriptor &source{staticSource.descriptor()};
+  source.Establish(structType, sizeof(int), srcData, 1, extent);
+  source.GetDimension(0).SetLowerBound(1);
+
+  ASSERT_DEATH(RTNAME(AssignSimple)(dest, source, __FILE__, __LINE__),
+      "AssignSimple: Cannot assign to derived type");
+}
+
+TEST(AssignSimpleCrash, CharacterType) {
+  auto dest{MakeArray<TypeCategory::Character, 1>(
+      std::vector<int>{3}, std::vector<char>{'a', 'b', 'c'}, sizeof(char))};
+  auto source{MakeArray<TypeCategory::Character, 1>(
+      std::vector<int>{3}, std::vector<char>{'x', 'y', 'z'}, sizeof(char))};
+  ASSERT_DEATH(RTNAME(AssignSimple)(*dest, *source, __FILE__, __LINE__),
+      "AssignSimple: Cannot assign to character type");
+}
+
+TEST(AssignSimpleCrash, NonAllocatableElementCountMismatch) {
+  // Non-allocatable arrays with different element counts
+  int destData[3] = {1, 2, 3};
+  int srcData[5] = {10, 20, 30, 40, 50};
+  TypeCode intType{TypeCategory::Integer, 4};
+
+  StaticDescriptor<1> staticDest;
+  Descriptor &dest{staticDest.descriptor()};
+  SubscriptValue destExtent[1]{3};
+  dest.Establish(intType, sizeof(int), destData, 1, destExtent);
+  dest.GetDimension(0).SetLowerBound(1);
+
+  StaticDescriptor<1> staticSource;
+  Descriptor &source{staticSource.descriptor()};
+  SubscriptValue srcExtent[1]{5};
+  source.Establish(intType, sizeof(int), srcData, 1, srcExtent);
+  source.GetDimension(0).SetLowerBound(1);
+
+  ASSERT_DEATH(RTNAME(AssignSimple)(dest, source, __FILE__, __LINE__),
+      "AssignSimple: mismatching element counts");
+}

>From 1122827a791a675be79e87dddc5fda57f1e946cf Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Thu, 20 Aug 2026 09:19:14 -0500
Subject: [PATCH 9/9] fix clang-format issue

---
 flang-rt/unittests/Runtime/Assign.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/flang-rt/unittests/Runtime/Assign.cpp b/flang-rt/unittests/Runtime/Assign.cpp
index 089736c84a029..8e9b89d7f661f 100644
--- a/flang-rt/unittests/Runtime/Assign.cpp
+++ b/flang-rt/unittests/Runtime/Assign.cpp
@@ -390,9 +390,8 @@ TEST(AssignSimpleCrash, ElementBytesMismatch) {
   // 4-byte integers vs 8-byte integers
   auto dest{MakeArray<TypeCategory::Integer, 4>(
       std::vector<int>{3}, std::vector<int>{1, 2, 3}, sizeof(int))};
-  auto source{MakeArray<TypeCategory::Integer, 8>(
-      std::vector<int>{3}, std::vector<std::int64_t>{1, 2, 3},
-      sizeof(std::int64_t))};
+  auto source{MakeArray<TypeCategory::Integer, 8>(std::vector<int>{3},
+      std::vector<std::int64_t>{1, 2, 3}, sizeof(std::int64_t))};
   ASSERT_DEATH(RTNAME(AssignSimple)(*dest, *source, __FILE__, __LINE__),
       "AssignSimple: ElementBytes mismatch");
 }



More information about the flang-commits mailing list