[flang-commits] [flang] [llvm] [flang] Fix incorrect DSE of VALUE dummy arg copy stores due to TBAA mismatch (PR #215366)
via flang-commits
flang-commits at lists.llvm.org
Mon Aug 10 11:56:05 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-fir-hlfir
Author: Daniel Chen (DanielCChen)
<details>
<summary>Changes</summary>
**Problem:**
When a BIND(C) derived-type dummy argument with the VALUE attribute is large
enough to be passed on the stack (e.g. PPC64 byval ABI), Flang lowers it as
a local `fir.alloca` with a `fir.declare` carrying
`fortran_attrs = value`. Before this fix, `AddAliasTags` classified accesses
to that alloca under the `"allocated data"` TBAA subtree
(`SourceKind::Allocate`). However, other accesses to the same dummy variable
were tagged under the `"dummy arg data"` subtree (`SourceKind::Argument`).
Because these two subtrees are siblings under `"any data access"`, LLVM's
`DSEPass` treated the copy-stores as non-aliasing with the subsequent reads
and incorrectly eliminated them. At `-O3`, reads of the VALUE copy returned
garbage, causing wrong results or `ERROR STOP`.
**Fix:**
in `AddAliasTags.cpp`, detect when a `SourceKind::Allocate` source is a
VALUE dummy arg copy by checking for `fir::FortranVariableFlagsEnum::value`
on its `fir.declare`. Tag such accesses under `dummyArgDataTree` instead of
`allocatedDataTree`, making them consistent with the dummy arg reads and
preventing DSE from eliminating the stores.
**Reproducer** (fails at `-O3`, passes at `-O0`, before fix):
```fortran
module m
use ISO_C_BINDING
type, bind(c) :: dtd0; integer(C_INT_LEAST8_T) :: a(10,5); end type
type, bind(c) :: dtd1
integer(C_INT_LEAST8_T) :: a(10,5); type(dtd0) :: d0
end type
end module m
program p
use m
interface
integer(C_INT_LEAST8_T) function fnt4(dt) bind(c)
import; type(dtd1), value :: dt
end function
end interface
type(dtd1) :: dtb; integer ret
dtb%a = 1_1; dtb%d0%a = 1_1
ret = fnt4(dtb)
end program p
integer(C_INT_LEAST8_T) function fnt4(dt) bind(c)
use ISO_C_BINDING, only: C_INT_LEAST8_T
use m, only: dtd0, dtd1
type(dtd1), value :: dt
do i = 1, 5; do j = 1, 10
if (dt%a(j,i) /= 1) ERROR STOP 156
dt%a(j,i) = 2
if (dt%d0%a(j,i) /= 1) ERROR STOP 158
dt%d0%a(j,i) = 2
end do; end do
fnt4 = 0
end function fnt4
---
Full diff: https://github.com/llvm/llvm-project/pull/215366.diff
2 Files Affected:
- (modified) flang/lib/Optimizer/Transforms/AddAliasTags.cpp (+35)
- (added) tbaa-value-dummy-arg.fir (+50)
``````````diff
diff --git a/flang/lib/Optimizer/Transforms/AddAliasTags.cpp b/flang/lib/Optimizer/Transforms/AddAliasTags.cpp
index 44eea630416d0..db216634ac7a5 100644
--- a/flang/lib/Optimizer/Transforms/AddAliasTags.cpp
+++ b/flang/lib/Optimizer/Transforms/AddAliasTags.cpp
@@ -827,6 +827,28 @@ void AddAliasTagsPass::runOnAliasInterface(fir::FirAliasTagOpInterface op,
else
unknownAllocOp = true;
+ // Check if this allocation is a local copy of a VALUE dummy argument.
+ // A VALUE dummy arg is lowered as a local alloca declared with the
+ // fir::FortranVariableFlagsEnum::value flag. Accesses through this copy
+ // must be tagged under dummyArgDataTree (not allocatedDataTree) so they
+ // are consistent with other accesses to the same dummy variable, which
+ // are also tagged under dummyArgDataTree. Using allocatedDataTree here
+ // makes the copy-stores and dummy-arg reads appear non-aliasing to DSE,
+ // which then incorrectly eliminates the copy stores.
+ bool isValueDummyCopy = false;
+ if (!unknownAllocOp) {
+ mlir::Value sourceVal = llvm::cast<mlir::Value>(source.origin.u);
+ if (fir::DeclareOp declareOp = getDeclareOp(sourceVal)) {
+ auto varIf = mlir::cast<fir::FortranVariableOpInterface>(
+ declareOp.getOperation());
+ auto fortranAttrs = varIf.getFortranAttrs();
+ if (fortranAttrs &&
+ bitEnumContainsAny(*fortranAttrs,
+ fir::FortranVariableFlagsEnum::value))
+ isValueDummyCopy = true;
+ }
+ }
+
if (unknownAllocOp) {
LLVM_DEBUG(llvm::dbgs().indent(2)
<< "WARN: unknown defining op for SourceKind::Allocate " << *op
@@ -845,6 +867,19 @@ void AddAliasTagsPass::runOnAliasInterface(fir::FirAliasTagOpInterface op,
<< "WARN: couldn't find a name for TARGET allocation " << *op
<< "\n");
tag = state.getFuncTreeWithScope(func, scopeOp).targetDataTree.getTag();
+ } else if (isValueDummyCopy && name && state.attachLocalAllocTag()) {
+ // VALUE dummy arg copy: tag under dummyArgDataTree to match the
+ // dummy arg reads, preventing DSE from treating stores as dead.
+ LLVM_DEBUG(llvm::dbgs().indent(2)
+ << "Found reference to VALUE dummy copy " << name << " at "
+ << *op << "\n");
+ tag = state.getFuncTreeWithScope(func, scopeOp)
+ .dummyArgDataTree.getTag(*name);
+ } else if (isValueDummyCopy && state.attachLocalAllocTag()) {
+ LLVM_DEBUG(llvm::dbgs().indent(2)
+ << "WARN: couldn't find name for VALUE dummy copy at " << *op
+ << "\n");
+ tag = state.getFuncTreeWithScope(func, scopeOp).dummyArgDataTree.getTag();
} else if (name && state.attachLocalAllocTag()) {
LLVM_DEBUG(llvm::dbgs().indent(2) << "Found reference to allocation "
<< name << " at " << *op << "\n");
diff --git a/tbaa-value-dummy-arg.fir b/tbaa-value-dummy-arg.fir
new file mode 100644
index 0000000000000..793ddf60cd4cd
--- /dev/null
+++ b/tbaa-value-dummy-arg.fir
@@ -0,0 +1,50 @@
+// Test that TBAA tags for a VALUE dummy argument local copy alloca are placed
+// under "dummy arg data" (not "allocated data"), so that DSEPass cannot
+// incorrectly treat the copy-stores as dead relative to dummy-arg reads.
+//
+// The VALUE dummy arg is lowered as a local fir.alloca with
+// fortran_attrs = #fir.var_attrs<value>. Both the copy-store into the alloca
+// and the subsequent loads from it must be tagged under "dummy arg data",
+// not "allocated data". Before the fix, the alloca was classified as
+// SourceKind::Allocate and received an "allocated data" tag, which is a
+// sibling of "dummy arg data" under "any data access", causing DSE to
+// incorrectly eliminate the copy stores.
+
+// RUN: fir-opt --fir-add-alias-tags %s | FileCheck %s
+
+// CHECK: #[[ROOT:.+]] = #llvm.tbaa_root<id = "Flang function root fnt4">
+// CHECK: #[[ANYACCESS:.+]] = #llvm.tbaa_type_desc<id = "any access", members = {<#[[ROOT]], 0>}>
+// CHECK: #[[ANYDATA:.+]] = #llvm.tbaa_type_desc<id = "any data access", members = {<#[[ANYACCESS]], 0>}>
+// CHECK: #[[DUMMYARG:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data", members = {<#[[ANYDATA]], 0>}>
+// CHECK-NOT: "allocated data"
+
+// The copy-store and the load from the VALUE alloca must both use
+// a "dummy arg data" tag, not an "allocated data" tag.
+// CHECK-LABEL: func.func @fnt4(
+// CHECK: fir.store {{.*}} {tbaa = [#{{.+}}]}
+// CHECK: fir.load {{.*}} {tbaa = [#{{.+}}]}
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<!llvm.ptr = dense<64> : vector<4xi64>, i1 = dense<8> : vector<2xi64>, i8 = dense<8> : vector<2xi64>, i16 = dense<16> : vector<2xi64>, i32 = dense<32> : vector<2xi64>, i64 = dense<[32, 64]> : vector<2xi64>, f16 = dense<16> : vector<2xi64>, f64 = dense<64> : vector<2xi64>, f128 = dense<128> : vector<2xi64>, "dlti.endianness" = "big">, llvm.data_layout = ""} {
+
+// Represents:
+// subroutine fnt4(dt) bind(c)
+// type(dtd1), value :: dt ! large struct passed on stack (byval)
+// ...
+// end subroutine
+func.func @fnt4(%arg0: !fir.ref<!fir.type<_QMmxisob27bTdtd1{a:!fir.array<50xi8>,d0:!fir.type<_QMmxisob27bTdtd0{a:!fir.array<50xi8>}>}>> {fir.bindc_name = "dt"}) {
+ %0 = fir.dummy_scope : !fir.dscope
+ // VALUE dummy: local alloca copy, declared with fortran_attrs = value
+ %1 = fir.alloca !fir.type<_QMmxisob27bTdtd1{a:!fir.array<50xi8>,d0:!fir.type<_QMmxisob27bTdtd0{a:!fir.array<50xi8>}>}> {bindc_name = "dt", uniq_name = "_QFfnt4Edt"}
+ %2 = fir.declare %1 {fortran_attrs = #fir.var_attrs<value>, uniq_name = "_QFfnt4Edt"} : (!fir.ref<!fir.type<_QMmxisob27bTdtd1{a:!fir.array<50xi8>,d0:!fir.type<_QMmxisob27bTdtd0{a:!fir.array<50xi8>}>}>>) -> !fir.ref<!fir.type<_QMmxisob27bTdtd1{a:!fir.array<50xi8>,d0:!fir.type<_QMmxisob27bTdtd0{a:!fir.array<50xi8>}>}>>
+ // Copy incoming byval arg into the local alloca — this store must be
+ // tagged under "dummy arg data" so DSE cannot treat it as dead.
+ %3 = fir.load %arg0 : !fir.ref<!fir.type<_QMmxisob27bTdtd1{a:!fir.array<50xi8>,d0:!fir.type<_QMmxisob27bTdtd0{a:!fir.array<50xi8>}>}>>
+ fir.store %3 to %2 : !fir.ref<!fir.type<_QMmxisob27bTdtd1{a:!fir.array<50xi8>,d0:!fir.type<_QMmxisob27bTdtd0{a:!fir.array<50xi8>}>}>>
+ // Load dt%a from the VALUE copy — must share the same TBAA subtree.
+ %4 = fir.coordinate_of %2, a : (!fir.ref<!fir.type<_QMmxisob27bTdtd1{a:!fir.array<50xi8>,d0:!fir.type<_QMmxisob27bTdtd0{a:!fir.array<50xi8>}>}>>) -> !fir.ref<!fir.array<50xi8>>
+ %c0 = arith.constant 0 : index
+ %5 = fir.coordinate_of %4, %c0 : (!fir.ref<!fir.array<50xi8>>, index) -> !fir.ref<i8>
+ %6 = fir.load %5 : !fir.ref<i8>
+ return
+}
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/215366
More information about the flang-commits
mailing list