[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