[llvm-branch-commits] [flang] [flang] - Call _FortranAAssignSimple instead of _FortranAAssign for intrinsic-type array assignments (PR #213705)

Pranav Bhandarkar via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Aug 18 10:41:51 PDT 2026


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

>From 42febe11ccb2073a1b517f74d00af0c79e935ea1 Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Sun, 2 Aug 2026 00:27:19 -0500
Subject: [PATCH] [flang] - Call _FortranAAssignSimple instead of
 _FortranAAssign for intrinsic-type array assignments.

This patch adds support for calling _FortranAAssignSimple, a faster-path for array assignments.
`_FortranAAssignSimple` is called when ALL the following 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

Otherwise, uses `_FortranAAssign` (or specialized variants like `_FortranAAssignPolymorphic`, `_FortranAAssignExplicitLengthCharacter`).

This is a (perhaps final) part of the fix for https://github.com/llvm/llvm-project/issues/203915
---
 .../flang/Optimizer/Builder/Runtime/Assign.h  |  12 +
 .../lib/Optimizer/Builder/Runtime/Assign.cpp  |  13 +
 .../HLFIR/Transforms/ConvertToFIR.cpp         |  50 ++-
 .../Optimizer/OpenMP/LowerWorkdistribute.cpp  |  32 +-
 flang/test/HLFIR/assign-codegen.fir           |   4 +-
 flang/test/HLFIR/assign-simple-routing.fir    | 287 ++++++++++++++++++
 flang/test/HLFIR/fir-local-alloca-block.fir   |   2 +-
 ...workdistribute-saxpy-and-scalar-assign.f90 |   2 +-
 8 files changed, 381 insertions(+), 21 deletions(-)
 create mode 100644 flang/test/HLFIR/assign-simple-routing.fir

diff --git a/flang/include/flang/Optimizer/Builder/Runtime/Assign.h b/flang/include/flang/Optimizer/Builder/Runtime/Assign.h
index 52a6a1d8e5a02..fda941c3261e5 100644
--- a/flang/include/flang/Optimizer/Builder/Runtime/Assign.h
+++ b/flang/include/flang/Optimizer/Builder/Runtime/Assign.h
@@ -63,5 +63,17 @@ void genCopyInAssign(fir::FirOpBuilder &builder, mlir::Location loc,
 void genCopyOutAssign(fir::FirOpBuilder &builder, mlir::Location loc,
                       mlir::Value varBoxAddr, mlir::Value tempBoxAddr);
 
+/// Generate runtime call to AssignSimple (fast path for intrinsic types).
+/// \p destBox must be a fir.ref<fir.box<T>> and \p sourceBox a fir.box<T>.
+/// Preconditions enforced at call site:
+///   - Intrinsic element type (integer, real, complex, logical)
+///   - Matching ranks (no scalar-to-array broadcasting)
+///   - Same element byte size
+///   - Non-volatile
+/// Runtime handles: contiguous and non-contiguous layouts, aliasing detection,
+/// allocatable reallocation.
+void genAssignSimple(fir::FirOpBuilder &builder, mlir::Location loc,
+                     mlir::Value destBox, mlir::Value sourceBox);
+
 } // namespace fir::runtime
 #endif // FORTRAN_OPTIMIZER_BUILDER_RUNTIME_ASSIGN_H
diff --git a/flang/lib/Optimizer/Builder/Runtime/Assign.cpp b/flang/lib/Optimizer/Builder/Runtime/Assign.cpp
index 336dbdc89c04a..fc9c6b0eb51e3 100644
--- a/flang/lib/Optimizer/Builder/Runtime/Assign.cpp
+++ b/flang/lib/Optimizer/Builder/Runtime/Assign.cpp
@@ -95,3 +95,16 @@ void fir::runtime::genCopyOutAssign(fir::FirOpBuilder &builder,
                                             sourceBox, sourceFile, sourceLine);
   fir::CallOp::create(builder, loc, func, args);
 }
+
+void fir::runtime::genAssignSimple(fir::FirOpBuilder &builder,
+                                   mlir::Location loc, mlir::Value destBox,
+                                   mlir::Value sourceBox) {
+  auto func = fir::runtime::getRuntimeFunc<mkRTKey(AssignSimple)>(loc, builder);
+  auto fTy = func.getFunctionType();
+  auto sourceFile = fir::factory::locationToFilename(builder, loc);
+  auto sourceLine =
+      fir::factory::locationToLineNo(builder, loc, fTy.getInput(3));
+  auto args = fir::runtime::createArguments(builder, loc, fTy, destBox,
+                                            sourceBox, sourceFile, sourceLine);
+  fir::CallOp::create(builder, loc, func, args);
+}
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
index 29c209f4c338e..19142a77d0b5b 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
@@ -29,6 +29,10 @@ namespace hlfir {
 #define GEN_PASS_DEF_CONVERTHLFIRTOFIR
 #include "flang/Optimizer/HLFIR/Passes.h.inc"
 } // namespace hlfir
+static llvm::cl::opt<bool> useFortranAssignOnly(
+    "use-fortran-assign-only",
+    llvm::cl::desc("Do not use _FortranAAssignSimple. Only _FortranAAssign"),
+    llvm::cl::init(false));
 
 using namespace mlir;
 
@@ -145,7 +149,26 @@ class AssignOpConversion : public mlir::OpRewritePattern<hlfir::AssignOp> {
           // type after the assignment.
           fir::runtime::genAssignPolymorphic(builder, loc, to, from);
         } else {
-          fir::runtime::genAssign(builder, loc, to, from);
+          // Use simple path for allocatable with trivial types (scalars and
+          // arrays) Only use Simple path when ranks match. Only use Simple path
+          // for non-volatile - volatile needs memory ordering NOTE: For
+          // allocatables, we assume contiguity - allocatable whole-array
+          // assignments
+          //       are always contiguous. Strided sections of allocatables go
+          //       through different path.
+          if (!lhs.isPolymorphic() &&
+              fir::isa_trivial(lhs.getFortranElementType()) &&
+              lhs.getRank() == rhs.getRank() &&
+              !fir::isa_volatile_type(lhs.getType()) &&
+              !cuf::getDataAttr(lhs.getDefiningOp())) {
+            // Simple intrinsic type allocatable with matching ranks,
+            // non-volatile, non-polymorphic.
+            fir::runtime::genAssignSimple(builder, loc, to, from);
+          } else {
+            // Complex: derived types, polymorphic, rank mismatch
+            // (scalar-to-array), volatile, etc.
+            fir::runtime::genAssign(builder, loc, to, from);
+          }
         }
       }
     } else if (lhs.isArray() ||
@@ -169,10 +192,29 @@ class AssignOpConversion : public mlir::OpRewritePattern<hlfir::AssignOp> {
       // reference.
       auto toMutableBox = builder.createTemporary(loc, to.getType());
       fir::StoreOp::create(builder, loc, to, toMutableBox);
-      if (assignOp.isTemporaryLHS())
+      if (assignOp.isTemporaryLHS()) {
         fir::runtime::genAssignTemporary(builder, loc, toMutableBox, from);
-      else
-        fir::runtime::genAssign(builder, loc, toMutableBox, from);
+      } else {
+        // Use simple path for non-allocatable arrays with trivial types
+        // CRITICAL: Only use Simple path when ranks match - scalar-to-array
+        // requires broadcasting CRITICAL: Only use Simple path for non-volatile
+        // - volatile needs memory ordering NOTE: Contiguity is now handled at
+        // runtime in AssignSimple
+        if (!useFortranAssignOnly && !lhs.isPolymorphic() &&
+            fir::isa_trivial(lhs.getFortranElementType()) &&
+            lhs.getRank() == rhs.getRank() &&
+            !fir::isa_volatile_type(lhs.getType()) &&
+            !cuf::getDataAttr(lhs.getDefiningOp())) {
+          // Simple intrinsic type array with matching ranks, non-volatile,
+          // non-polymorphic. AssignSimple handles both contiguous (fast
+          // memmove) and non-contiguous (element-wise)
+          fir::runtime::genAssignSimple(builder, loc, toMutableBox, from);
+        } else {
+          // Complex: polymorphic, derived type, rank mismatch
+          // (scalar-to-array), volatile
+          fir::runtime::genAssign(builder, loc, toMutableBox, from);
+        }
+      }
     } else {
       // TODO: use the type specification to see if IsFinalizable is set,
       // or propagate IsFinalizable attribute from lowering.
diff --git a/flang/lib/Optimizer/OpenMP/LowerWorkdistribute.cpp b/flang/lib/Optimizer/OpenMP/LowerWorkdistribute.cpp
index 68e997e3abde4..5af2d1ddb5f50 100644
--- a/flang/lib/Optimizer/OpenMP/LowerWorkdistribute.cpp
+++ b/flang/lib/Optimizer/OpenMP/LowerWorkdistribute.cpp
@@ -57,6 +57,14 @@ namespace {
 
 /// This string is used to identify the Fortran-specific runtime FortranAAssign.
 static constexpr llvm::StringRef FortranAssignStr = "_FortranAAssign";
+static constexpr llvm::StringRef FortranAssignSimpleStr =
+    "_FortranAAssignSimple";
+
+/// Check if the function name is any variant of Fortran assignment runtime
+/// call.
+static bool isFortranAssignCall(llvm::StringRef funcName) {
+  return funcName == FortranAssignStr || funcName == FortranAssignSimpleStr;
+}
 
 /// The isRuntimeCall function is a utility designed to determine
 /// if a given operation is a call to a Fortran-specific runtime function.
@@ -76,11 +84,11 @@ static bool isRuntimeCall(Operation *op) {
 /// operation nested in an omp.workdistribute region.
 /// Parallelize here refers to dividing into units of work.
 static bool shouldParallelize(Operation *op) {
-  // True if the op is a runtime call to Assign
+  // True if the op is a runtime call to Assign (any variant)
   if (isRuntimeCall(op)) {
     fir::CallOp runtimeCall = cast<fir::CallOp>(op);
     auto funcName = runtimeCall.getCallee()->getRootReference().getValue();
-    if (funcName == FortranAssignStr) {
+    if (isFortranAssignCall(funcName)) {
       return true;
     }
   }
@@ -167,15 +175,13 @@ verifyTargetTeamsWorkdistribute(omp::WorkdistributeOp workdistribute) {
     if (auto callOp = dyn_cast<fir::CallOp>(op)) {
       if (isRuntimeCall(&op)) {
         auto funcName = (*callOp.getCallee()).getRootReference().getValue();
-        // _FortranAAssign is handled. Other runtime calls are not supported
-        // in omp.workdistribute yet.
-        if (funcName == FortranAssignStr)
+        // _FortranAAssign and _FortranAAssignSimple are handled.
+        // Other runtime calls are not supported in omp.workdistribute yet.
+        if (isFortranAssignCall(funcName))
           continue;
-        else {
-          emitError(loc, "Runtime call " + funcName +
-                             " lowering not supported for workdistribute yet.");
-          return failure();
-        }
+        emitError(loc, "Runtime call " + funcName +
+                           " lowering not supported for workdistribute yet.");
+        return failure();
       }
     }
   }
@@ -620,7 +626,7 @@ workdistributeRuntimeCallLower(omp::WorkdistributeOp workdistribute,
       rewriter.setInsertionPoint(&op);
       fir::CallOp runtimeCall = cast<fir::CallOp>(op);
       auto funcName = runtimeCall.getCallee()->getRootReference().getValue();
-      if (funcName == FortranAssignStr) {
+      if (isFortranAssignCall(funcName)) {
         if (isFortranAssignSrcScalarAndDestArray(runtimeCall) && targetOp) {
           // Record the target ops to process later
           targetOpsToProcess.insert(targetOp);
@@ -1342,7 +1348,7 @@ static LogicalResult moveToHost(omp::TargetOp targetOp, RewriterBase &rewriter,
     if (isRuntimeCall(clonedOp)) {
       fir::CallOp runtimeCall = cast<fir::CallOp>(op);
       auto funcName = runtimeCall.getCallee()->getRootReference().getValue();
-      if (funcName == FortranAssignStr) {
+      if (isFortranAssignCall(funcName)) {
         opsToReplace.push_back(clonedOp);
       } else {
         emitError(runtimeCall->getLoc(), "Unhandled runtime call hoisting.");
@@ -1392,7 +1398,7 @@ static LogicalResult moveToHost(omp::TargetOp targetOp, RewriterBase &rewriter,
     else if (isRuntimeCall(op)) {
       fir::CallOp runtimeCall = cast<fir::CallOp>(op);
       auto funcName = runtimeCall.getCallee()->getRootReference().getValue();
-      if (funcName == FortranAssignStr) {
+      if (isFortranAssignCall(funcName)) {
         rewriter.setInsertionPoint(op);
         fir::FirOpBuilder builder{rewriter, op};
 
diff --git a/flang/test/HLFIR/assign-codegen.fir b/flang/test/HLFIR/assign-codegen.fir
index cabe18ef98799..67be391327bdf 100644
--- a/flang/test/HLFIR/assign-codegen.fir
+++ b/flang/test/HLFIR/assign-codegen.fir
@@ -141,7 +141,7 @@ func.func @array(%arg0: !fir.box<!fir.array<?xi32>>, %arg1: !fir.ref<!fir.array<
 // CHECK:  fir.store %[[VAL_6]] to %[[VAL_2]] : !fir.ref<!fir.box<!fir.array<?xi32>>>
 // CHECK:  %[[VAL_26:.*]] = fir.convert %[[VAL_2]] : (!fir.ref<!fir.box<!fir.array<?xi32>>>) -> !fir.ref<!fir.box<none>>
 // CHECK:  %[[VAL_27:.*]] = fir.convert %[[VAL_10]] : (!fir.box<!fir.array<100xi32>>) -> !fir.box<none>
-// CHECK:  fir.call @_FortranAAssign(%[[VAL_26]], %[[VAL_27]], %{{.*}}, %{{.*}}) : (!fir.ref<!fir.box<none>>, !fir.box<none>, !fir.ref<i8>, i32) -> ()
+// CHECK:  fir.call @_FortranAAssignSimple(%[[VAL_26]], %[[VAL_27]], %{{.*}}, %{{.*}}) : (!fir.ref<!fir.box<none>>, !fir.box<none>, !fir.ref<i8>, i32) -> ()
 
 
 func.func @array_temp(%arg0: !fir.box<!fir.array<?xi32>>, %arg1: !fir.ref<!fir.array<100xi32>>) {
@@ -206,7 +206,7 @@ func.func @alloc_assign(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>,
 // CHECK-SAME: %[[VAL_1:.*]]: !fir.box<!fir.array<?xi32>>) {
 // CHECK:  %[[VAL_2:.*]] = fir.convert %[[VAL_0]] : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>) -> !fir.ref<!fir.box<none>>
 // CHECK:  %[[VAL_3:.*]] = fir.convert %[[VAL_1]] : (!fir.box<!fir.array<?xi32>>) -> !fir.box<none>
-// CHECK:  fir.call @_FortranAAssign(%[[VAL_2]], %[[VAL_3]], %{{.*}}, %{{.*}}) : (!fir.ref<!fir.box<none>>, !fir.box<none>, !fir.ref<i8>, i32) -> ()
+// CHECK:  fir.call @_FortranAAssignSimple(%[[VAL_2]], %[[VAL_3]], %{{.*}}, %{{.*}}) : (!fir.ref<!fir.box<none>>, !fir.box<none>, !fir.ref<i8>, i32) -> ()
 
 func.func @alloc_assign_temp(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, %arg1: !fir.box<!fir.array<?xi32>>) {
   hlfir.assign %arg1 to %arg0 realloc temporary_lhs : !fir.box<!fir.array<?xi32>>, !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
diff --git a/flang/test/HLFIR/assign-simple-routing.fir b/flang/test/HLFIR/assign-simple-routing.fir
new file mode 100644
index 0000000000000..963f54cd61e0f
--- /dev/null
+++ b/flang/test/HLFIR/assign-simple-routing.fir
@@ -0,0 +1,287 @@
+// Test routing decisions for _FortranAAssignSimple vs _FortranAAssign in
+// hlfir.assign lowering to FIR. The routing logic lives in ConvertToFIR.cpp
+// and has two main decision points:
+//
+//   1. Allocatable path (isAllocatableAssignment): routes to AssignSimple when
+//      isa_trivial(elementType) && ranksMatch && !volatile
+//
+//   2. Non-allocatable array path (lhs.isArray()): routes to AssignSimple when
+//      !polymorphic && isa_trivial(elementType) && ranksMatch && !volatile
+//
+// This file verifies every boundary condition of those routing decisions.
+
+// RUN: fir-opt %s -convert-hlfir-to-fir | FileCheck %s
+
+// ---------------------------------------------------------------------------
+// POSITIVE CASES: should route to _FortranAAssignSimple
+// ---------------------------------------------------------------------------
+
+// Allocatable rank-1 i32 array: trivial integer type, ranks match (1==1),
+// non-volatile.  This is the canonical "simple allocatable" case.
+func.func @alloc_i32_array(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>,
+                            %arg1: !fir.box<!fir.array<?xi32>>) {
+  hlfir.assign %arg1 to %arg0 realloc : !fir.box<!fir.array<?xi32>>,
+      !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+  return
+}
+// CHECK-LABEL: func.func @alloc_i32_array(
+// CHECK:       fir.call @_FortranAAssignSimple(
+// CHECK-NOT:   fir.call @_FortranAAssign(
+
+// ---------------------------------------------------------------------------
+
+// Allocatable rank-1 f64 array: trivial real type, ranks match (1==1),
+// non-volatile.  Verifies that real (double-precision) types are routed simply.
+func.func @alloc_f64_array(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>,
+                            %arg1: !fir.box<!fir.array<?xf64>>) {
+  hlfir.assign %arg1 to %arg0 realloc : !fir.box<!fir.array<?xf64>>,
+      !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+  return
+}
+// CHECK-LABEL: func.func @alloc_f64_array(
+// CHECK:       fir.call @_FortranAAssignSimple(
+// CHECK-NOT:   fir.call @_FortranAAssign(
+
+// ---------------------------------------------------------------------------
+
+// Allocatable rank-1 complex<f32> array: trivial complex type, ranks match,
+// non-volatile.  Verifies complex intrinsic types (MLIR ComplexType) are
+// routed simply — isa_complex() checks mlir::ComplexType with float element.
+func.func @alloc_complex_array(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xcomplex<f32>>>>>,
+                                %arg1: !fir.box<!fir.array<?xcomplex<f32>>>) {
+  hlfir.assign %arg1 to %arg0 realloc : !fir.box<!fir.array<?xcomplex<f32>>>,
+      !fir.ref<!fir.box<!fir.heap<!fir.array<?xcomplex<f32>>>>>
+  return
+}
+// CHECK-LABEL: func.func @alloc_complex_array(
+// CHECK:       fir.call @_FortranAAssignSimple(
+// CHECK-NOT:   fir.call @_FortranAAssign(
+
+// ---------------------------------------------------------------------------
+
+// Allocatable rank-1 logical<4> array: trivial logical type, ranks match,
+// non-volatile.  Verifies logical intrinsic types are routed simply.
+func.func @alloc_logical_array(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.logical<4>>>>>,
+                                %arg1: !fir.box<!fir.array<?x!fir.logical<4>>>) {
+  hlfir.assign %arg1 to %arg0 realloc : !fir.box<!fir.array<?x!fir.logical<4>>>,
+      !fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.logical<4>>>>>
+  return
+}
+// CHECK-LABEL: func.func @alloc_logical_array(
+// CHECK:       fir.call @_FortranAAssignSimple(
+// CHECK-NOT:   fir.call @_FortranAAssign(
+
+// ---------------------------------------------------------------------------
+
+// Allocatable rank-2 i32 array: trivial integer type, ranks match (2==2),
+// non-volatile.  Verifies that the rank-match check works for rank-2.
+func.func @alloc_i32_2d(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xi32>>>>,
+                         %arg1: !fir.box<!fir.array<?x?xi32>>) {
+  hlfir.assign %arg1 to %arg0 realloc : !fir.box<!fir.array<?x?xi32>>,
+      !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xi32>>>>
+  return
+}
+// CHECK-LABEL: func.func @alloc_i32_2d(
+// CHECK:       fir.call @_FortranAAssignSimple(
+// CHECK-NOT:   fir.call @_FortranAAssign(
+
+// ---------------------------------------------------------------------------
+
+// Non-allocatable rank-1 i32 array: non-polymorphic, trivial integer, ranks
+// match (1==1), non-volatile.  Canonical "simple non-allocatable array" case.
+func.func @nonalloc_i32_array(%arg0: !fir.box<!fir.array<?xi32>>,
+                               %arg1: !fir.ref<!fir.array<100xi32>>) {
+  %c100 = arith.constant 100 : index
+  %0:2 = hlfir.declare %arg0 {uniq_name = "x"} : (!fir.box<!fir.array<?xi32>>) -> (!fir.box<!fir.array<?xi32>>, !fir.box<!fir.array<?xi32>>)
+  %1 = fir.shape %c100 : (index) -> !fir.shape<1>
+  %2:2 = hlfir.declare %arg1(%1) {uniq_name = "y"} : (!fir.ref<!fir.array<100xi32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<100xi32>>, !fir.ref<!fir.array<100xi32>>)
+  hlfir.assign %2#0 to %0#0 : !fir.ref<!fir.array<100xi32>>, !fir.box<!fir.array<?xi32>>
+  return
+}
+// CHECK-LABEL: func.func @nonalloc_i32_array(
+// CHECK:       fir.call @_FortranAAssignSimple(
+// CHECK-NOT:   fir.call @_FortranAAssign(
+
+// ---------------------------------------------------------------------------
+
+// Non-allocatable rank-1 f32 array: non-polymorphic, trivial real, ranks
+// match (1==1), non-volatile.  Verifies f32 (single-precision) is routed simply.
+func.func @nonalloc_f32_array(%arg0: !fir.box<!fir.array<?xf32>>,
+                               %arg1: !fir.ref<!fir.array<100xf32>>) {
+  %c100 = arith.constant 100 : index
+  %0:2 = hlfir.declare %arg0 {uniq_name = "x"} : (!fir.box<!fir.array<?xf32>>) -> (!fir.box<!fir.array<?xf32>>, !fir.box<!fir.array<?xf32>>)
+  %1 = fir.shape %c100 : (index) -> !fir.shape<1>
+  %2:2 = hlfir.declare %arg1(%1) {uniq_name = "y"} : (!fir.ref<!fir.array<100xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<100xf32>>, !fir.ref<!fir.array<100xf32>>)
+  hlfir.assign %2#0 to %0#0 : !fir.ref<!fir.array<100xf32>>, !fir.box<!fir.array<?xf32>>
+  return
+}
+// CHECK-LABEL: func.func @nonalloc_f32_array(
+// CHECK:       fir.call @_FortranAAssignSimple(
+// CHECK-NOT:   fir.call @_FortranAAssign(
+
+// ---------------------------------------------------------------------------
+
+// Non-allocatable rank-1 logical<4> array: non-polymorphic, trivial logical,
+// ranks match (1==1), non-volatile.  Verifies logical types in the non-alloc path.
+func.func @nonalloc_logical(%arg0: !fir.box<!fir.array<?x!fir.logical<4>>>,
+                             %arg1: !fir.ref<!fir.array<100x!fir.logical<4>>>) {
+  %c100 = arith.constant 100 : index
+  %0:2 = hlfir.declare %arg0 {uniq_name = "x"} : (!fir.box<!fir.array<?x!fir.logical<4>>>) -> (!fir.box<!fir.array<?x!fir.logical<4>>>, !fir.box<!fir.array<?x!fir.logical<4>>>)
+  %1 = fir.shape %c100 : (index) -> !fir.shape<1>
+  %2:2 = hlfir.declare %arg1(%1) {uniq_name = "y"} : (!fir.ref<!fir.array<100x!fir.logical<4>>>, !fir.shape<1>) -> (!fir.ref<!fir.array<100x!fir.logical<4>>>, !fir.ref<!fir.array<100x!fir.logical<4>>>)
+  hlfir.assign %2#0 to %0#0 : !fir.ref<!fir.array<100x!fir.logical<4>>>, !fir.box<!fir.array<?x!fir.logical<4>>>
+  return
+}
+// CHECK-LABEL: func.func @nonalloc_logical(
+// CHECK:       fir.call @_FortranAAssignSimple(
+// CHECK-NOT:   fir.call @_FortranAAssign(
+
+// ---------------------------------------------------------------------------
+// NEGATIVE CASES: should NOT use AssignSimple, route to _FortranAAssign
+// ---------------------------------------------------------------------------
+
+// Allocatable derived type array: !isa_trivial(elementType).  Derived types
+// may need finalization/copy semantics so they cannot use the simple path.
+func.func @alloc_derived(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.type<t1{x:f32}>>>>>,
+                          %arg1: !fir.box<!fir.array<?x!fir.type<t1{x:f32}>>>) {
+  hlfir.assign %arg1 to %arg0 realloc : !fir.box<!fir.array<?x!fir.type<t1{x:f32}>>>,
+      !fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.type<t1{x:f32}>>>>>
+  return
+}
+// CHECK-LABEL: func.func @alloc_derived(
+// CHECK:       fir.call @_FortranAAssign(
+// CHECK-NOT:   fir.call @_FortranAAssignSimple(
+
+// ---------------------------------------------------------------------------
+
+// Allocatable volatile i32 array: volatile flag is set on the ref itself.
+// The volatile is on !fir.ref<..., volatile>, so isa_volatile_type(lhs.getType())
+// returns true and AssignSimple cannot be used.
+func.func @alloc_volatile(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>, volatile>,
+                           %arg1: !fir.box<!fir.array<?xi32>>) {
+  hlfir.assign %arg1 to %arg0 realloc : !fir.box<!fir.array<?xi32>>,
+      !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>, volatile>
+  return
+}
+// CHECK-LABEL: func.func @alloc_volatile(
+// CHECK:       fir.call @_FortranAAssign(
+// CHECK-NOT:   fir.call @_FortranAAssignSimple(
+
+// ---------------------------------------------------------------------------
+
+// Allocatable scalar-to-array: rank mismatch (RHS rank 0, LHS rank 1).
+// Even though the element type is trivial (i32), the rank mismatch (scalar
+// RHS to array LHS) violates the lhs.getRank() == rhs.getRank() precondition
+// in the allocatable path, so this must route to the full _FortranAAssign
+// path to handle scalar broadcasting.
+func.func @alloc_scalar_to_array(%lhs: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>,
+                                  %rhs: i32) {
+  hlfir.assign %rhs to %lhs realloc : i32,
+      !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+  return
+}
+// CHECK-LABEL: func.func @alloc_scalar_to_array(
+// CHECK:       fir.call @_FortranAAssign(
+// CHECK-NOT:   fir.call @_FortranAAssignSimple(
+
+// ---------------------------------------------------------------------------
+
+// Non-allocatable scalar-to-array: rank mismatch (RHS rank 0, LHS rank 1).
+// Broadcasting a scalar across an array requires the full _FortranAAssign path.
+func.func @nonalloc_scalar_to_array(%lhs: !fir.box<!fir.array<?xi32>>, %rhs: i32) {
+  hlfir.assign %rhs to %lhs : i32, !fir.box<!fir.array<?xi32>>
+  return
+}
+// CHECK-LABEL: func.func @nonalloc_scalar_to_array(
+// CHECK:       fir.call @_FortranAAssign(
+// CHECK-NOT:   fir.call @_FortranAAssignSimple(
+
+// ---------------------------------------------------------------------------
+
+// Non-allocatable polymorphic array: lhs.isPolymorphic() is true.  Polymorphic
+// assignments must be handled by _FortranAAssign for correct dynamic type
+// semantics; AssignSimple is excluded by the !lhs.isPolymorphic() guard.
+func.func @nonalloc_polymorphic(%lhs: !fir.class<!fir.array<?x!fir.type<t2{y:i64}>>>,
+                                 %rhs: !fir.class<!fir.array<?x!fir.type<t2{y:i64}>>>) {
+  hlfir.assign %rhs to %lhs : !fir.class<!fir.array<?x!fir.type<t2{y:i64}>>>,
+      !fir.class<!fir.array<?x!fir.type<t2{y:i64}>>>
+  return
+}
+// CHECK-LABEL: func.func @nonalloc_polymorphic(
+// CHECK:       fir.call @_FortranAAssign(
+// CHECK-NOT:   fir.call @_FortranAAssignSimple(
+
+// ---------------------------------------------------------------------------
+
+// Non-allocatable volatile array: volatile flag prevents the simple path.
+// Volatile arrays need proper memory-ordering semantics.
+func.func @nonalloc_volatile_array(%lhs: !fir.box<!fir.array<?xi32>, volatile>,
+                                    %rhs: !fir.box<!fir.array<?xi32>>) {
+  hlfir.assign %rhs to %lhs : !fir.box<!fir.array<?xi32>>,
+      !fir.box<!fir.array<?xi32>, volatile>
+  return
+}
+// CHECK-LABEL: func.func @nonalloc_volatile_array(
+// CHECK:       fir.call @_FortranAAssign(
+// CHECK-NOT:   fir.call @_FortranAAssignSimple(
+
+// ---------------------------------------------------------------------------
+
+// Non-allocatable derived type array: !isa_trivial(elementType).  Derived
+// types in the non-allocatable array path also require the full assign path.
+func.func @nonalloc_derived_array(%lhs: !fir.box<!fir.array<?x!fir.type<t3{z:f32}>>>,
+                                   %rhs: !fir.box<!fir.array<?x!fir.type<t3{z:f32}>>>) {
+  hlfir.assign %rhs to %lhs : !fir.box<!fir.array<?x!fir.type<t3{z:f32}>>>,
+      !fir.box<!fir.array<?x!fir.type<t3{z:f32}>>>
+  return
+}
+// CHECK-LABEL: func.func @nonalloc_derived_array(
+// CHECK:       fir.call @_FortranAAssign(
+// CHECK-NOT:   fir.call @_FortranAAssignSimple(
+
+// ---------------------------------------------------------------------------
+// OTHER ASSIGN VARIANTS: tests for other runtime entry points
+// ---------------------------------------------------------------------------
+
+// Allocatable with temporary_lhs: routes to _FortranAAssignTemporary.
+// The temporary_lhs attribute indicates the LHS is a compiler-generated temp,
+// so reallocation must not finalize it; AssignTemporary handles this correctly.
+func.func @alloc_temporary(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>,
+                            %arg1: !fir.box<!fir.array<?xi32>>) {
+  hlfir.assign %arg1 to %arg0 realloc temporary_lhs : !fir.box<!fir.array<?xi32>>,
+      !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+  return
+}
+// CHECK-LABEL: func.func @alloc_temporary(
+// CHECK:       fir.call @_FortranAAssignTemporary(
+// CHECK-NOT:   fir.call @_FortranAAssignSimple(
+
+// ---------------------------------------------------------------------------
+
+// Allocatable polymorphic LHS: routes to _FortranAAssignPolymorphic.
+// Polymorphic allocatable assignments require setting the LHS dynamic type
+// to match the RHS, which only AssignPolymorphic does correctly.
+func.func @alloc_polymorphic(%lhs: !fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<t4{w:i32}>>>>>,
+                              %rhs: !fir.class<!fir.array<?x!fir.type<t4{w:i32}>>>) {
+  hlfir.assign %rhs to %lhs realloc : !fir.class<!fir.array<?x!fir.type<t4{w:i32}>>>,
+      !fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<t4{w:i32}>>>>>
+  return
+}
+// CHECK-LABEL: func.func @alloc_polymorphic(
+// CHECK:       fir.call @_FortranAAssignPolymorphic(
+// CHECK-NOT:   fir.call @_FortranAAssignSimple(
+
+// ---------------------------------------------------------------------------
+
+// Allocatable with keep_lhs_len: routes to _FortranAAssignExplicitLengthCharacter.
+// The keep_lhs_length_if_realloc attribute means the LHS character length must
+// not change on reallocation; this requires the dedicated character assign path.
+func.func @alloc_explicit_char(%lhs: !fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.char<1,10>>>>>,
+                                %rhs: !fir.box<!fir.array<?x!fir.char<1,?>>>) {
+  hlfir.assign %rhs to %lhs realloc keep_lhs_len : !fir.box<!fir.array<?x!fir.char<1,?>>>,
+      !fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.char<1,10>>>>>
+  return
+}
+// CHECK-LABEL: func.func @alloc_explicit_char(
+// CHECK:       fir.call @_FortranAAssignExplicitLengthCharacter(
+// CHECK-NOT:   fir.call @_FortranAAssignSimple(
diff --git a/flang/test/HLFIR/fir-local-alloca-block.fir b/flang/test/HLFIR/fir-local-alloca-block.fir
index 9d76e86fec3d9..36b9db9d309b5 100644
--- a/flang/test/HLFIR/fir-local-alloca-block.fir
+++ b/flang/test/HLFIR/fir-local-alloca-block.fir
@@ -29,6 +29,6 @@ func.func @foo() {
 // CHECK:      fir.store %[[VAL_6]] to %[[VAL_2]] : !fir.ref<![[TYPE]]>
 // CHECK:      %[[VAL_10:.*]] = fir.convert %[[VAL_2]] : (!fir.ref<![[TYPE]]>) -> !fir.ref<!fir.box<none>>
 // CHECK:      %[[VAL_11:.*]] = fir.convert %[[VAL_3]] : (![[TYPE]]) -> !fir.box<none>
-// CHECK:      fir.call @_FortranAAssign(%[[VAL_10]], %[[VAL_11]], %{{.*}}, %{{.*}})
+// CHECK:      fir.call @_FortranAAssignSimple(%[[VAL_10]], %[[VAL_11]], %{{.*}}, %{{.*}})
 // CHECK:      fir.yield(%[[VAL_1]] : !fir.ref<![[TYPE]]>)
 // CHECK:  }
diff --git a/flang/test/Lower/OpenMP/workdistribute-saxpy-and-scalar-assign.f90 b/flang/test/Lower/OpenMP/workdistribute-saxpy-and-scalar-assign.f90
index fd02e9c234180..09e3375efa487 100644
--- a/flang/test/Lower/OpenMP/workdistribute-saxpy-and-scalar-assign.f90
+++ b/flang/test/Lower/OpenMP/workdistribute-saxpy-and-scalar-assign.f90
@@ -75,5 +75,5 @@ end subroutine teams_workdistribute
 
 ! CHECK-O0-LABEL: func @_QPteams_workdistribute
 ! CHECK-O0: omp.wsloop
-! CHECK-O0: fir.call @_FortranAAssign({{.*}}%c50_i32)
+! CHECK-O0: fir.call @_FortranAAssignSimple({{.*}}%c50_i32)
 ! CHECK-O0: fir.call @_FortranAAssign({{.*}}%c58_i32)



More information about the llvm-branch-commits mailing list