Skip to content

[flang] Restructured TBAA trees in AddAliasTags pass. #136725

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 29, 2025

Conversation

vzakhari
Copy link
Contributor

This patch produces the following TBAA tree for a function:

  Function root
  |
  "any access"
  |
  |- "descriptor member"
  |- "any data access"
     |
     |- "dummy arg data"
     |- "target data"
        |
        |- "allocated data"
        |- "direct data"
        |- "global data"

The TBAA tags are assigned using the following logic:

  • All POINTER variables point to the root of "target data".
  • Dummy arguments without POINTER/TARGET point to their
    leafs under "dummy arg data".
  • Dummy arguments with TARGET point to the root of "target data".
  • Global variables without descriptors point to their leafs under
    "global data" (including the ones with TARGET).
  • Global variables with descriptors point to their leafs under
    "direct data" (including the ones with TARGET).
  • Locally allocated variables point to their leafs under
    "allocated data" (including the ones with TARGET).

This change makes it possible to disambiguate globals like:

  module data
    real, allocatable :: a(:)
    real, allocatable, target :: b(:)
  end

Indeed, two direct references to global vars cannot alias
even if any/both of them have TARGET attribute.
In addition, the dummy arguments without POINTER/TARGET cannot alias
any other variable even with POINTER/TARGET. This was not expressed
in TBAA before this change.

As before, any "unknown" memory references (such as with Indirect
source, as classified by FIR alias analysis) may alias with
anything, as long as they point to the root of "any access".

Please think of the counterexamples for which this structure
may not work.

This patch produces the following TBAA tree for a function:
  Function root
  |
  "any access"
  |
  |- "descriptor member"
  |- "any data access"
     |
     |- "dummy arg data"
     |- "target data"
        |
        |- "allocated data"
        |- "direct data"
        |- "global data"

The TBAA tags are assigned using the following logic:
  * All POINTER variables point to the root of "target data".
  * Dummy arguments without POINTER/TARGET point to their
    leafs under "dummy arg data".
  * Dummy arguments with TARGET point to the root of "target data".
  * Global variables without descriptors point to their leafs under
    "global data" (including the ones with TARGET).
  * Global variables with descriptors point to their leafs under
    "direct data" (including the ones with TARGET).
  * Locally allocated variables point to their leafs under
   "allocated data" (including the ones with TARGET).

This change makes it possible to disambiguate globals like:
  module data
    real, allocatable :: a(:)
    real, allocatable, target :: b(:)
  end

Indeed, two direct references to global vars cannot alias
even if any/both of them have TARGET attribute.
In addition, the dummy arguments without POINTER/TARGET cannot alias
any other variable even with POINTER/TARGET. This was not expressed
in TBAA before this change.

As before, any "unknown" memory references (such as with Indirect
source, as classified by FIR alias analysis) may alias with
anything, as long as they point to the root of "any access".

Please think of the counterexamples for which this structure
may not work.
@vzakhari vzakhari requested review from tblah and jeanPerier April 22, 2025 16:10
@llvmbot llvmbot added flang Flang issues not falling into any other category flang:fir-hlfir labels Apr 22, 2025
@llvmbot
Copy link
Member

llvmbot commented Apr 22, 2025

@llvm/pr-subscribers-flang-fir-hlfir

Author: Slava Zakharin (vzakhari)

Changes

This patch produces the following TBAA tree for a function:

  Function root
  |
  "any access"
  |
  |- "descriptor member"
  |- "any data access"
     |
     |- "dummy arg data"
     |- "target data"
        |
        |- "allocated data"
        |- "direct data"
        |- "global data"

The TBAA tags are assigned using the following logic:

  • All POINTER variables point to the root of "target data".
  • Dummy arguments without POINTER/TARGET point to their
    leafs under "dummy arg data".
  • Dummy arguments with TARGET point to the root of "target data".
  • Global variables without descriptors point to their leafs under
    "global data" (including the ones with TARGET).
  • Global variables with descriptors point to their leafs under
    "direct data" (including the ones with TARGET).
  • Locally allocated variables point to their leafs under
    "allocated data" (including the ones with TARGET).

This change makes it possible to disambiguate globals like:

  module data
    real, allocatable :: a(:)
    real, allocatable, target :: b(:)
  end

Indeed, two direct references to global vars cannot alias
even if any/both of them have TARGET attribute.
In addition, the dummy arguments without POINTER/TARGET cannot alias
any other variable even with POINTER/TARGET. This was not expressed
in TBAA before this change.

As before, any "unknown" memory references (such as with Indirect
source, as classified by FIR alias analysis) may alias with
anything, as long as they point to the root of "any access".

Please think of the counterexamples for which this structure
may not work.


Patch is 59.86 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/136725.diff

10 Files Affected:

  • (modified) flang/include/flang/Optimizer/Analysis/AliasAnalysis.h (+6)
  • (modified) flang/include/flang/Optimizer/Analysis/TBAAForest.h (+34-1)
  • (modified) flang/lib/Optimizer/Analysis/AliasAnalysis.cpp (+8)
  • (modified) flang/lib/Optimizer/Analysis/TBAAForest.cpp (+11-6)
  • (modified) flang/lib/Optimizer/Transforms/AddAliasTags.cpp (+35-16)
  • (modified) flang/test/Transforms/tbaa-with-dummy-scope.fir (+41-130)
  • (modified) flang/test/Transforms/tbaa-with-dummy-scope2.fir (+6-4)
  • (modified) flang/test/Transforms/tbaa.fir (+6-6)
  • (modified) flang/test/Transforms/tbaa2.fir (+11-8)
  • (added) flang/test/Transforms/tbaa3.fir (+331)
diff --git a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
index c71988d081dd0..30dd5f754d0b5 100644
--- a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
+++ b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
@@ -155,6 +155,12 @@ struct AliasAnalysis {
     /// Return true, if Target or Pointer attribute is set.
     bool isTargetOrPointer() const;
 
+    /// Return true, if Target attribute is set.
+    bool isTarget() const;
+
+    /// Return true, if Pointer attribute is set.
+    bool isPointer() const;
+
     bool isDummyArgument() const;
     bool isData() const;
     bool isBoxData() const;
diff --git a/flang/include/flang/Optimizer/Analysis/TBAAForest.h b/flang/include/flang/Optimizer/Analysis/TBAAForest.h
index f95fcffd96e93..4d2281642b43d 100644
--- a/flang/include/flang/Optimizer/Analysis/TBAAForest.h
+++ b/flang/include/flang/Optimizer/Analysis/TBAAForest.h
@@ -40,6 +40,12 @@ struct TBAATree {
 
     mlir::LLVM::TBAATagAttr getTag(llvm::StringRef uniqueId) const;
 
+    /// Create a TBAA tag pointing to the root of this subtree,
+    /// i.e. all the children tags will alias with this tag.
+    mlir::LLVM::TBAATagAttr getTag() const;
+
+    mlir::LLVM::TBAATypeDescriptorAttr getRoot() const { return parent; }
+
   private:
     SubtreeState(mlir::MLIRContext *ctx, std::string name,
                  mlir::LLVM::TBAANodeAttr grandParent)
@@ -51,17 +57,44 @@ struct TBAATree {
     const std::string parentId;
     mlir::MLIRContext *const context;
     mlir::LLVM::TBAATypeDescriptorAttr parent;
-    llvm::DenseMap<llvm::StringRef, mlir::LLVM::TBAATagAttr> tagDedup;
   };
 
+  /// A subtree for POINTER/TARGET variables data.
+  /// Any POINTER variable must use a tag that points
+  /// to the root of this subtree.
+  /// A TARGET dummy argument must also point to this root.
+  SubtreeState targetDataTree;
+  /// A subtree for global variables data (e.g. user module variables).
   SubtreeState globalDataTree;
+  /// A subtree for variables allocated via fir.alloca or fir.allocmem.
   SubtreeState allocatedDataTree;
+  /// A subtree for subprogram's dummy arguments.
+  /// It only contains children for the dummy arguments
+  /// that are not POINTER/TARGET. They all do not conflict
+  /// with each other and with any other data access, except
+  /// with unknown data accesses (FIR alias analysis uses
+  /// SourceKind::Indirect for sources of such accesses).
   SubtreeState dummyArgDataTree;
+  /// A subtree for global variables descriptors.
   SubtreeState directDataTree;
   mlir::LLVM::TBAATypeDescriptorAttr anyAccessDesc;
   mlir::LLVM::TBAATypeDescriptorAttr boxMemberTypeDesc;
   mlir::LLVM::TBAATypeDescriptorAttr anyDataTypeDesc;
 
+  // Structure of the created tree:
+  //   Function root
+  //   |
+  //   "any access"
+  //   |
+  //   |- "descriptor member"
+  //   |- "any data access"
+  //      |
+  //      |- "dummy arg data"
+  //      |- "target data"
+  //         |
+  //         |- "allocated data"
+  //         |- "direct data"
+  //         |- "global data"
   static TBAATree buildTree(mlir::StringAttr functionName);
 
 private:
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index bda98871a1c7a..cbfc8b63ab64d 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -110,6 +110,14 @@ bool AliasAnalysis::Source::isTargetOrPointer() const {
          attributes.test(Attribute::Target);
 }
 
+bool AliasAnalysis::Source::isTarget() const {
+  return attributes.test(Attribute::Target);
+}
+
+bool AliasAnalysis::Source::isPointer() const {
+  return attributes.test(Attribute::Pointer);
+}
+
 bool AliasAnalysis::Source::isDummyArgument() const {
   if (auto v = origin.u.dyn_cast<mlir::Value>()) {
     return fir::isDummyArgument(v);
diff --git a/flang/lib/Optimizer/Analysis/TBAAForest.cpp b/flang/lib/Optimizer/Analysis/TBAAForest.cpp
index 786c4932ea89e..cce50e0de1bc7 100644
--- a/flang/lib/Optimizer/Analysis/TBAAForest.cpp
+++ b/flang/lib/Optimizer/Analysis/TBAAForest.cpp
@@ -11,9 +11,6 @@
 
 mlir::LLVM::TBAATagAttr
 fir::TBAATree::SubtreeState::getTag(llvm::StringRef uniqueName) const {
-  // mlir::LLVM::TBAATagAttr &tag = tagDedup[uniqueName];
-  // if (tag)
-  //   return tag;
   std::string id = (parentId + "/" + uniqueName).str();
   mlir::LLVM::TBAATypeDescriptorAttr type =
       mlir::LLVM::TBAATypeDescriptorAttr::get(
@@ -22,6 +19,10 @@ fir::TBAATree::SubtreeState::getTag(llvm::StringRef uniqueName) const {
   // return tag;
 }
 
+mlir::LLVM::TBAATagAttr fir::TBAATree::SubtreeState::getTag() const {
+  return mlir::LLVM::TBAATagAttr::get(parent, parent, 0);
+}
+
 fir::TBAATree fir::TBAATree::buildTree(mlir::StringAttr func) {
   llvm::StringRef funcName = func.getValue();
   std::string rootId = ("Flang function root " + funcName).str();
@@ -53,9 +54,13 @@ fir::TBAATree fir::TBAATree::buildTree(mlir::StringAttr func) {
 fir::TBAATree::TBAATree(mlir::LLVM::TBAATypeDescriptorAttr anyAccess,
                         mlir::LLVM::TBAATypeDescriptorAttr dataRoot,
                         mlir::LLVM::TBAATypeDescriptorAttr boxMemberTypeDesc)
-    : globalDataTree(dataRoot.getContext(), "global data", dataRoot),
-      allocatedDataTree(dataRoot.getContext(), "allocated data", dataRoot),
+    : targetDataTree(dataRoot.getContext(), "target data", dataRoot),
+      globalDataTree(dataRoot.getContext(), "global data",
+                     targetDataTree.getRoot()),
+      allocatedDataTree(dataRoot.getContext(), "allocated data",
+                        targetDataTree.getRoot()),
       dummyArgDataTree(dataRoot.getContext(), "dummy arg data", dataRoot),
-      directDataTree(dataRoot.getContext(), "direct data", dataRoot),
+      directDataTree(dataRoot.getContext(), "direct data",
+                     targetDataTree.getRoot()),
       anyAccessDesc(anyAccess), boxMemberTypeDesc(boxMemberTypeDesc),
       anyDataTypeDesc(dataRoot) {}
diff --git a/flang/lib/Optimizer/Transforms/AddAliasTags.cpp b/flang/lib/Optimizer/Transforms/AddAliasTags.cpp
index e6fc2ed992e38..66b4b84998801 100644
--- a/flang/lib/Optimizer/Transforms/AddAliasTags.cpp
+++ b/flang/lib/Optimizer/Transforms/AddAliasTags.cpp
@@ -198,12 +198,6 @@ void AddAliasTagsPass::runOnAliasInterface(fir::FirAliasTagOpInterface op,
   LLVM_DEBUG(llvm::dbgs() << "Analysing " << op << "\n");
 
   const fir::AliasAnalysis::Source &source = state.getSource(memref);
-  if (source.isTargetOrPointer()) {
-    LLVM_DEBUG(llvm::dbgs().indent(2) << "Skipping TARGET/POINTER\n");
-    // These will get an "any data access" tag in TBAABuilder (CodeGen): causing
-    // them to "MayAlias" with all non-box accesses
-    return;
-  }
 
   // Process the scopes, if not processed yet.
   state.processFunctionScopes(func);
@@ -228,15 +222,21 @@ void AddAliasTagsPass::runOnAliasInterface(fir::FirAliasTagOpInterface op,
     LLVM_DEBUG(llvm::dbgs().indent(2)
                << "Found reference to dummy argument at " << *op << "\n");
     std::string name = getFuncArgName(llvm::cast<mlir::Value>(source.origin.u));
-    if (!name.empty())
+    // If it is a TARGET or POINTER, then we do not care about the name,
+    // because the tag points to the root of the subtree currently.
+    if (source.isTargetOrPointer()) {
+      tag = state.getFuncTreeWithScope(func, scopeOp).targetDataTree.getTag();
+    } else if (!name.empty()) {
       tag = state.getFuncTreeWithScope(func, scopeOp)
                 .dummyArgDataTree.getTag(name);
-    else
+    } else {
       LLVM_DEBUG(llvm::dbgs().indent(2)
                  << "WARN: couldn't find a name for dummy argument " << *op
                  << "\n");
+      tag = state.getFuncTreeWithScope(func, scopeOp).dummyArgDataTree.getTag();
+    }
 
-    // TBAA for global variables
+    // TBAA for global variables without descriptors
   } else if (enableGlobals &&
              source.kind == fir::AliasAnalysis::SourceKind::Global &&
              !source.isBoxData()) {
@@ -244,9 +244,13 @@ void AddAliasTagsPass::runOnAliasInterface(fir::FirAliasTagOpInterface op,
     const char *name = glbl.getRootReference().data();
     LLVM_DEBUG(llvm::dbgs().indent(2) << "Found reference to global " << name
                                       << " at " << *op << "\n");
-    tag = state.getFuncTreeWithScope(func, scopeOp).globalDataTree.getTag(name);
+    if (source.isPointer())
+      tag = state.getFuncTreeWithScope(func, scopeOp).targetDataTree.getTag();
+    else
+      tag =
+          state.getFuncTreeWithScope(func, scopeOp).globalDataTree.getTag(name);
 
-    // TBAA for SourceKind::Direct
+    // TBAA for global variables with descriptors
   } else if (enableDirect &&
              source.kind == fir::AliasAnalysis::SourceKind::Global &&
              source.isBoxData()) {
@@ -254,11 +258,12 @@ void AddAliasTagsPass::runOnAliasInterface(fir::FirAliasTagOpInterface op,
       const char *name = glbl.getRootReference().data();
       LLVM_DEBUG(llvm::dbgs().indent(2) << "Found reference to direct " << name
                                         << " at " << *op << "\n");
-      tag =
-          state.getFuncTreeWithScope(func, scopeOp).directDataTree.getTag(name);
+      if (source.isPointer())
+        tag = state.getFuncTreeWithScope(func, scopeOp).targetDataTree.getTag();
+      else
+        tag = state.getFuncTreeWithScope(func, scopeOp)
+                  .directDataTree.getTag(name);
     } else {
-      // SourceKind::Direct is likely to be extended to cases which are not a
-      // SymbolRefAttr in the future
       LLVM_DEBUG(llvm::dbgs().indent(2) << "Can't get name for direct "
                                         << source << " at " << *op << "\n");
     }
@@ -269,11 +274,23 @@ void AddAliasTagsPass::runOnAliasInterface(fir::FirAliasTagOpInterface op,
     std::optional<llvm::StringRef> name;
     mlir::Operation *sourceOp =
         llvm::cast<mlir::Value>(source.origin.u).getDefiningOp();
+    bool unknownAllocOp = false;
     if (auto alloc = mlir::dyn_cast_or_null<fir::AllocaOp>(sourceOp))
       name = alloc.getUniqName();
     else if (auto alloc = mlir::dyn_cast_or_null<fir::AllocMemOp>(sourceOp))
       name = alloc.getUniqName();
-    if (name) {
+    else
+      unknownAllocOp = true;
+
+    if (unknownAllocOp) {
+      LLVM_DEBUG(llvm::dbgs().indent(2)
+                 << "WARN: unknown defining op for SourceKind::Allocate " << *op
+                 << "\n");
+    } else if (source.isPointer()) {
+      LLVM_DEBUG(llvm::dbgs().indent(2)
+                 << "Found reference to allocation at " << *op << "\n");
+      tag = state.getFuncTreeWithScope(func, scopeOp).targetDataTree.getTag();
+    } else if (name) {
       LLVM_DEBUG(llvm::dbgs().indent(2) << "Found reference to allocation "
                                         << name << " at " << *op << "\n");
       tag = state.getFuncTreeWithScope(func, scopeOp)
@@ -282,6 +299,8 @@ void AddAliasTagsPass::runOnAliasInterface(fir::FirAliasTagOpInterface op,
       LLVM_DEBUG(llvm::dbgs().indent(2)
                  << "WARN: couldn't find a name for allocation " << *op
                  << "\n");
+      tag =
+          state.getFuncTreeWithScope(func, scopeOp).allocatedDataTree.getTag();
     }
   } else {
     if (source.kind != fir::AliasAnalysis::SourceKind::Argument &&
diff --git a/flang/test/Transforms/tbaa-with-dummy-scope.fir b/flang/test/Transforms/tbaa-with-dummy-scope.fir
index 738692814cde5..7624de9431e08 100644
--- a/flang/test/Transforms/tbaa-with-dummy-scope.fir
+++ b/flang/test/Transforms/tbaa-with-dummy-scope.fir
@@ -2,7 +2,7 @@
 
 // subroutine test(x, y)
 //   real, target :: x, y
-//   x = y ! the load/store do not have TBAA due to TARGET
+//   x = y
 //   call inner(x, y) ! the inlined load/store go to Scope 1
 //   call inner(x, y) ! the inlined load/store go to Scope 2
 // contains
@@ -12,25 +12,30 @@
 //   end subroutine inner
 // end subroutine test
 
+// CHECK: #[[TEST1ROOT:.+]] = #llvm.tbaa_root<id = "Flang function root test1">
 // CHECK: #[[$ATTR_0:.+]] = #llvm.tbaa_root<id = "Flang function root test1 - Scope 1">
 // CHECK: #[[$ATTR_1:.+]] = #llvm.tbaa_root<id = "Flang function root test1 - Scope 2">
-// CHECK: #[[$ATTR_2:.+]] = #llvm.tbaa_type_desc<id = "any access", members = {<#tbaa_root, 0>}>
-// CHECK: #[[$ATTR_3:.+]] = #llvm.tbaa_type_desc<id = "any access", members = {<#tbaa_root1, 0>}>
-// CHECK: #[[$ATTR_4:.+]] = #llvm.tbaa_type_desc<id = "any data access", members = {<#tbaa_type_desc, 0>}>
-// CHECK: #[[$ATTR_5:.+]] = #llvm.tbaa_type_desc<id = "any data access", members = {<#tbaa_type_desc1, 0>}>
-// CHECK: #[[$ATTR_6:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data", members = {<#tbaa_type_desc2, 0>}>
-// CHECK: #[[$ATTR_7:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data", members = {<#tbaa_type_desc3, 0>}>
-// CHECK: #[[$ATTR_8:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QFtestFinnerEy", members = {<#tbaa_type_desc4, 0>}>
-// CHECK: #[[$ATTR_9:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QFtestFinnerEx", members = {<#tbaa_type_desc4, 0>}>
-// CHECK: #[[$ATTR_10:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QFtestFinnerEy", members = {<#tbaa_type_desc5, 0>}>
-// CHECK: #[[$ATTR_11:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QFtestFinnerEx", members = {<#tbaa_type_desc5, 0>}>
-// CHECK: #[[$ATTR_12:.+]] = #llvm.tbaa_tag<base_type = #tbaa_type_desc6, access_type = #tbaa_type_desc6, offset = 0>
-// CHECK: #[[$ATTR_13:.+]] = #llvm.tbaa_tag<base_type = #tbaa_type_desc7, access_type = #tbaa_type_desc7, offset = 0>
-// CHECK: #[[$ATTR_14:.+]] = #llvm.tbaa_tag<base_type = #tbaa_type_desc8, access_type = #tbaa_type_desc8, offset = 0>
-// CHECK: #[[$ATTR_15:.+]] = #llvm.tbaa_tag<base_type = #tbaa_type_desc9, access_type = #tbaa_type_desc9, offset = 0>
+// CHECK: #[[TEST1ANYACCESS:.+]] = #llvm.tbaa_type_desc<id = "any access", members = {<#[[TEST1ROOT]], 0>}>
+// CHECK: #[[$ATTR_2:.+]] = #llvm.tbaa_type_desc<id = "any access", members = {<#[[$ATTR_0]], 0>}>
+// CHECK: #[[$ATTR_3:.+]] = #llvm.tbaa_type_desc<id = "any access", members = {<#[[$ATTR_1]], 0>}>
+// CHECK: #[[TEST1ANYDATA:.+]] = #llvm.tbaa_type_desc<id = "any data access", members = {<#[[TEST1ANYACCESS]], 0>}>
+// CHECK: #[[$ATTR_4:.+]] = #llvm.tbaa_type_desc<id = "any data access", members = {<#[[$ATTR_2]], 0>}>
+// CHECK: #[[$ATTR_5:.+]] = #llvm.tbaa_type_desc<id = "any data access", members = {<#[[$ATTR_3]], 0>}>
+// CHECK: #[[TARGETDATA:.+]] = #llvm.tbaa_type_desc<id = "target data", members = {<#[[TEST1ANYDATA]], 0>}>
+// CHECK: #[[$ATTR_6:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data", members = {<#[[$ATTR_4]], 0>}>
+// CHECK: #[[$ATTR_7:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data", members = {<#[[$ATTR_5]], 0>}>
+// CHECK: #[[TARGETTAG:.+]] = #llvm.tbaa_tag<base_type = #[[TARGETDATA]], access_type = #[[TARGETDATA]], offset = 0>
+// CHECK: #[[$ATTR_8:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QFtestFinnerEy", members = {<#[[$ATTR_6]], 0>}>
+// CHECK: #[[$ATTR_9:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QFtestFinnerEx", members = {<#[[$ATTR_6]], 0>}>
+// CHECK: #[[$ATTR_10:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QFtestFinnerEy", members = {<#[[$ATTR_7]], 0>}>
+// CHECK: #[[$ATTR_11:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QFtestFinnerEx", members = {<#[[$ATTR_7]], 0>}>
+// CHECK: #[[$ATTR_12:.+]] = #llvm.tbaa_tag<base_type = #[[$ATTR_8]], access_type = #[[$ATTR_8]], offset = 0>
+// CHECK: #[[$ATTR_13:.+]] = #llvm.tbaa_tag<base_type = #[[$ATTR_9]], access_type = #[[$ATTR_9]], offset = 0>
+// CHECK: #[[$ATTR_14:.+]] = #llvm.tbaa_tag<base_type = #[[$ATTR_10]], access_type = #[[$ATTR_10]], offset = 0>
+// CHECK: #[[$ATTR_15:.+]] = #llvm.tbaa_tag<base_type = #[[$ATTR_11]], access_type = #[[$ATTR_11]], offset = 0>
 // CHECK:   func.func @test1(
-// CHECK:           %[[VAL_5:.*]] = fir.load %{{.*}} : !fir.ref<f32>
-// CHECK:           fir.store %{{.*}} : !fir.ref<f32>
+// CHECK:           %[[VAL_5:.*]] = fir.load %{{.*}} {tbaa = [#[[TARGETTAG]]]} : !fir.ref<f32>
+// CHECK:           fir.store %{{.*}} {tbaa = [#[[TARGETTAG]]]} : !fir.ref<f32>
 // CHECK:           %[[VAL_6:.*]] = fir.dummy_scope : !fir.dscope
 // CHECK:           %[[VAL_9:.*]] = fir.load %{{.*}} {tbaa = [#[[$ATTR_12]]]} : !fir.ref<f32>
 // CHECK:           fir.store %{{.*}} {tbaa = [#[[$ATTR_13]]]} : !fir.ref<f32>
@@ -58,102 +63,6 @@ func.func @test1(%arg0: !fir.ref<f32> {fir.bindc_name = "x", fir.target}, %arg1:
 
 // -----
 
-// Check that without proper fir.dummy_scope placement
-// we just put everything into the root scope.
-
-// CHECK: #[[$ATTR_16:.+]] = #llvm.tbaa_root<id = "Flang function root test2">
-// CHECK: #[[$ATTR_17:.+]] = #llvm.tbaa_type_desc<id = "any access", members = {<#tbaa_root, 0>}>
-// CHECK: #[[$ATTR_18:.+]] = #llvm.tbaa_type_desc<id = "any data access", members = {<#tbaa_type_desc, 0>}>
-// CHECK: #[[$ATTR_19:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data", members = {<#tbaa_type_desc1, 0>}>
-// CHECK: #[[$ATTR_20:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QFtestEy", members = {<#tbaa_type_desc2, 0>}>
-// CHECK: #[[$ATTR_21:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QFtestEx", members = {<#tbaa_type_desc2, 0>}>
-// CHECK: #[[$ATTR_22:.+]] = #llvm.tbaa_tag<base_type = #tbaa_type_desc3, access_type = #tbaa_type_desc3, offset = 0>
-// CHECK: #[[$ATTR_23:.+]] = #llvm.tbaa_tag<base_type = #tbaa_type_desc4, access_type = #tbaa_type_desc4, offset = 0>
-// CHECK:   func.func @test2(
-// CHECK:           %[[VAL_4:.*]] = fir.load %{{.*}} {tbaa = [#[[$ATTR_22]]]} : !fir.ref<f32>
-// CHECK:           fir.store %{{.*}} {tbaa = [#[[$ATTR_23]]]} : !fir.ref<f32>
-// CHECK:           %[[VAL_5:.*]] = fir.declare
-// CHECK:           %[[VAL_6:.*]] = fir.declare
-// CHECK:           %[[VAL_7:.*]] = fir.load %{{.*}} {tbaa = [#[[$ATTR_22]]]} : !fir.ref<f32>
-// CHECK:           fir.store %{{.*}} {tbaa = [#[[$ATTR_23]]]} : !fir.ref<f32>
-func.func @test2(%arg0: !fir.ref<f32> {fir.bindc_name = "x"}, %arg1: !fir.ref<f32> {fir.bindc_name = "y"}) {
-  %0 = fir.declare %arg0 {uniq_name = "_QFtestEx"} : (!fir.ref<f32>) -> !fir.ref<f32>
-  %1 = fir.declare %arg1 {uniq_name = "_QFtestEy"} : (!fir.ref<f32>) -> !fir.ref<f32>
-  %2 = fir.load %1 : !fir.ref<f32>
-  fir.store %2 to %0 : !fir.ref<f32>
-  %3 = fir.declare %0 {uniq_name = "_QFtestFinnerEx"} : (!fir.ref<f32>) -> !fir.ref<f32>
-  %4 = fir.declare %1 {uniq_name = "_QFtestFinnerEy"} : (!fir.ref<f32>) -> !fir.ref<f32>
-  %5 = fir.load %4 : !fir.ref<f32>
-  fir.store %5 to %3 : !fir.ref<f32>
-  return
-}
-
-// -----
-
-// module test
-//   real :: x, y
-// contains
-// subroutine caller
-//   x = y ! the load/store go to the root scope
-//   call callee
-// end subroutine caller
-// subroutine callee
-//   x = y ! the load/store go to the root scope
-//   ! Since there are no dummy arguments in callee,
-//   ! it is better to put the load/store into the
-//   ! enclosing root scope, so that they can be
-//   ! disambiguated using TBAA with the loads/stores
-//   ! in the enclosing scope.
-// end subroutine callee
-// end module test
-
-// CHECK: #[[$ATTR_24:.+]] = #llvm.tbaa_root<id = "Flang function root _QMtestPcaller">
-// CHECK: #[[$ATTR_25:.+]] = #llvm.tbaa_type_desc<id = "any access", members = {<#tbaa_root, 0>}>
-// CHECK: #[[$ATTR_26:.+]] = #llvm.tbaa_type_desc<id = "any data access", members = {<#tbaa_type_desc, 0>}>
-// CHECK: #[[$ATTR_27:.+]] = #llvm.tbaa_type_desc<id = "global data", members = {<#tbaa_type_desc1, 0>}>
-// CHECK: #[[$ATTR_28:.+]] = #llvm.tbaa_type_desc<id = "global data/_QMtestEy", members = {<#tbaa_type_desc2, 0>}>
-// CHECK: #[[$ATTR_29:.+]] = #llvm.tbaa_type_desc<id = "global data/_QMtestEx", members = {<#tbaa_type_desc2, 0>}>
-// CHECK: #[[$ATTR_30:.+]] = #llvm.tbaa_tag<base_type = #tbaa_type_desc3, access_type = #tbaa_type_desc3, offset = 0>
-// CHECK: #[[$ATTR_31:.+]] = #llvm.tbaa_tag<base_type = #tbaa_type_desc4, access_type = #tbaa_type_desc4, offset = 0>
-// CHECK:   func.func @_QMtestPcaller() {
-// CHECK:           %[[VAL_0:.*]] = fir.address_of(@_QMtestEx) : !fir.ref<f32>
-// CHECK:           %[[VAL_1:.*]] = fir.declare %[[VAL_0]] {uniq_name = "_QMtestEx"} : (!fir.ref<f32>) -> !fir.ref<f32>
-// CHECK:           %[[VAL_2:.*]] = fir.address_of(@_QMtestEy) : !fir.ref<f32>
-// CHECK:           %[[VAL_3:.*]...
[truncated]

Copy link
Contributor

@jeanPerier jeanPerier left a comment

Choose a reason for hiding this comment

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

Makes sense to me. Does it work ok with LLVM inlining?
Please wait for @tblah who knows much more than me about TBAA.

// | |- "global data/_QMdataEglob"
// | |- "global data/_QMdataEglobt"
// |
// |- "direct data" - "direct data/_QMdataEgloba"
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: extra - "direct data/_QMdataEgloba"?

Comment on lines +72 to +76
// |
// |- "allocated data/_QFtest1Elocal"
// |- "allocated data/_QFtest1Elocalt"
// |- "allocated data/_QFtest1Elocala"
// |- "allocated data/_QFtest1Elocalat"
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if creating tags for each variables in "allocated data" and "global data" really makes a difference. I would imagine LLVM is already clever enough to know/deduce that two global addresses cannot alias if they do not come from the same global (and the same with alloca). I am not against it, but I imagine this is growing the metadata size a bit.

Also, for common block, will it create different tags for each common block members, or just one with the common block global name? Since they technically share the same storage, it would be safer to have a single tag for them I think. Same with local/global equivalences, but here I think we should probably fall into the POINTER cases currently (worth adding a test though).

I totally understand why adding tags for each variables makes a difference in the "direct data" and "dummy arg data" cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wonder if creating tags for each variables in "allocated data" and "global data" really makes a difference. I would imagine LLVM is already clever enough to know/deduce that two global addresses cannot alias if they do not come from the same global (and the same with alloca). I am not against it, but I imagine this is growing the metadata size a bit.

I think it does not make a difference, indeed. I will experiment with this in a separate PR. Thanks for noticing it!

Also, for common block, will it create different tags for each common block members, or just one with the common block global name? Since they technically share the same storage, it would be safer to have a single tag for them I think. Same with local/global equivalences, but here I think we should probably fall into the POINTER cases currently (worth adding a test though).

I will add a new test for common and equivalence. In short:

  • Yes, we will create a single type descriptor and a tag for all member of the same common block.
  • We will also create a single type descriptor and a tag for all member of an equivalence, though, accesses of local equivalences won't have any tag unless --local-alloc-tbaa experimental option is enabled.

I want to try to enable --local-alloc-tbaa by default as well, but I will have to make sure WRF and other tests pass.

@vzakhari
Copy link
Contributor Author

Makes sense to me. Does it work ok with LLVM inlining?

Almost missed the question :)

Yes, it should work same way with LLVM inlining as before, but that does not mean our current TBAA approach is valid right now.

Our TBAA is invalid for the following case:

subroutine callee(x,y)
  real :: x, y
  x = 1.0
  y = 2.0
end subroutine callee

subroutine test(x,y)
  real, target :: x(2), y(3)
  call callee(x(1), y(1))
  call callee(x(2), y(2))
end subroutine test

Consider the case where LOC(x(1)) == LOC(y(2)). After LLVM inlining we will have:

define void @test_(ptr writeonly captures(none) %0, ptr writeonly captures(none) %1) local_unnamed_addr !dbg !4 {
  store float 1.000000e+00, ptr %0, align 4, !dbg !14, !tbaa !19
  store float 2.000000e+00, ptr %1, align 4, !dbg !25, !tbaa !26
  %3 = getelementptr i8, ptr %0, i64 4, !dbg !28
  %4 = getelementptr i8, ptr %1, i64 4, !dbg !28
  store float 1.000000e+00, ptr %3, align 4, !dbg !29, !tbaa !19
  store float 2.000000e+00, ptr %4, align 4, !dbg !31, !tbaa !26
  ret void, !dbg !32
}

So it says that the first and last stores do not alias, which is not true.

Copy link
Contributor

@jeanPerier jeanPerier left a comment

Choose a reason for hiding this comment

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

Thanks for the new tests and answers, looks great to me

@tblah
Copy link
Contributor

tblah commented Apr 25, 2025

Thanks for this reorganization Slava. I'm worried about the "'Target' hole for dummy arguments" section of the aliasing documentation. If I understand correctly, a dummy argument which does not have the TARGET attribute could still be associated with an actual argument which does, and so it might alias with any variable with the POINTER attribute. So I think POINTER variables still need to be added as "any data".

@tblah tblah requested a review from DominikAdamski April 25, 2025 13:27
@vzakhari
Copy link
Contributor Author

Thanks for this reorganization Slava. I'm worried about the "'Target' hole for dummy arguments" section of the aliasing documentation. If I understand correctly, a dummy argument which does not have the TARGET attribute could still be associated with an actual argument which does, and so it might alias with any variable with the POINTER attribute. So I think POINTER variables still need to be added as "any data".

Thank you for the review, Tom.

When a dummy argument does not have TARGET attribute and the underlying (sub)object is modified then it can be done only through referencing this dummy argument. If such a dummy argument is passed as an actual argument for associating it with the callee's dummy TARGET argument, then all the accesses to the callee's dummy argument will be attributed with TBAA having a different TBAA root (for the callee function), so the accesses within the callee and withing the caller will have disjoint TBAA that means MayAlias. So even if LLVM inlines the callee, I believe, the TBAA will be conservatively correct for such case.

Copy link
Contributor

@tblah tblah left a comment

Choose a reason for hiding this comment

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

Thanks for the clarification Slava. LGTM

@vzakhari vzakhari merged commit 82c036e into llvm:main Apr 29, 2025
11 checks passed
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
This patch produces the following TBAA tree for a function:
```
  Function root
  |
  "any access"
  |
  |- "descriptor member"
  |- "any data access"
     |
     |- "dummy arg data"
     |- "target data"
        |
        |- "allocated data"
        |- "direct data"
        |- "global data"
```
The TBAA tags are assigned using the following logic:
  * All POINTER variables point to the root of "target data".
  * Dummy arguments without POINTER/TARGET point to their
    leafs under "dummy arg data".
  * Dummy arguments with TARGET point to the root of "target data".
  * Global variables without descriptors point to their leafs under
    "global data" (including the ones with TARGET).
  * Global variables with descriptors point to their leafs under
    "direct data" (including the ones with TARGET).
  * Locally allocated variables point to their leafs under
   "allocated data" (including the ones with TARGET).

This change makes it possible to disambiguate globals like:
```
  module data
    real, allocatable :: a(:)
    real, allocatable, target :: b(:)
  end
```

Indeed, two direct references to global vars cannot alias
even if any/both of them have TARGET attribute.
In addition, the dummy arguments without POINTER/TARGET cannot alias
any other variable even with POINTER/TARGET. This was not expressed
in TBAA before this change.

As before, any "unknown" memory references (such as with Indirect
source, as classified by FIR alias analysis) may alias with
anything, as long as they point to the root of "any access".

Please think of the counterexamples for which this structure
may not work.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
This patch produces the following TBAA tree for a function:
```
  Function root
  |
  "any access"
  |
  |- "descriptor member"
  |- "any data access"
     |
     |- "dummy arg data"
     |- "target data"
        |
        |- "allocated data"
        |- "direct data"
        |- "global data"
```
The TBAA tags are assigned using the following logic:
  * All POINTER variables point to the root of "target data".
  * Dummy arguments without POINTER/TARGET point to their
    leafs under "dummy arg data".
  * Dummy arguments with TARGET point to the root of "target data".
  * Global variables without descriptors point to their leafs under
    "global data" (including the ones with TARGET).
  * Global variables with descriptors point to their leafs under
    "direct data" (including the ones with TARGET).
  * Locally allocated variables point to their leafs under
   "allocated data" (including the ones with TARGET).

This change makes it possible to disambiguate globals like:
```
  module data
    real, allocatable :: a(:)
    real, allocatable, target :: b(:)
  end
```

Indeed, two direct references to global vars cannot alias
even if any/both of them have TARGET attribute.
In addition, the dummy arguments without POINTER/TARGET cannot alias
any other variable even with POINTER/TARGET. This was not expressed
in TBAA before this change.

As before, any "unknown" memory references (such as with Indirect
source, as classified by FIR alias analysis) may alias with
anything, as long as they point to the root of "any access".

Please think of the counterexamples for which this structure
may not work.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
This patch produces the following TBAA tree for a function:
```
  Function root
  |
  "any access"
  |
  |- "descriptor member"
  |- "any data access"
     |
     |- "dummy arg data"
     |- "target data"
        |
        |- "allocated data"
        |- "direct data"
        |- "global data"
```
The TBAA tags are assigned using the following logic:
  * All POINTER variables point to the root of "target data".
  * Dummy arguments without POINTER/TARGET point to their
    leafs under "dummy arg data".
  * Dummy arguments with TARGET point to the root of "target data".
  * Global variables without descriptors point to their leafs under
    "global data" (including the ones with TARGET).
  * Global variables with descriptors point to their leafs under
    "direct data" (including the ones with TARGET).
  * Locally allocated variables point to their leafs under
   "allocated data" (including the ones with TARGET).

This change makes it possible to disambiguate globals like:
```
  module data
    real, allocatable :: a(:)
    real, allocatable, target :: b(:)
  end
```

Indeed, two direct references to global vars cannot alias
even if any/both of them have TARGET attribute.
In addition, the dummy arguments without POINTER/TARGET cannot alias
any other variable even with POINTER/TARGET. This was not expressed
in TBAA before this change.

As before, any "unknown" memory references (such as with Indirect
source, as classified by FIR alias analysis) may alias with
anything, as long as they point to the root of "any access".

Please think of the counterexamples for which this structure
may not work.
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
This patch produces the following TBAA tree for a function:
```
  Function root
  |
  "any access"
  |
  |- "descriptor member"
  |- "any data access"
     |
     |- "dummy arg data"
     |- "target data"
        |
        |- "allocated data"
        |- "direct data"
        |- "global data"
```
The TBAA tags are assigned using the following logic:
  * All POINTER variables point to the root of "target data".
  * Dummy arguments without POINTER/TARGET point to their
    leafs under "dummy arg data".
  * Dummy arguments with TARGET point to the root of "target data".
  * Global variables without descriptors point to their leafs under
    "global data" (including the ones with TARGET).
  * Global variables with descriptors point to their leafs under
    "direct data" (including the ones with TARGET).
  * Locally allocated variables point to their leafs under
   "allocated data" (including the ones with TARGET).

This change makes it possible to disambiguate globals like:
```
  module data
    real, allocatable :: a(:)
    real, allocatable, target :: b(:)
  end
```

Indeed, two direct references to global vars cannot alias
even if any/both of them have TARGET attribute.
In addition, the dummy arguments without POINTER/TARGET cannot alias
any other variable even with POINTER/TARGET. This was not expressed
in TBAA before this change.

As before, any "unknown" memory references (such as with Indirect
source, as classified by FIR alias analysis) may alias with
anything, as long as they point to the root of "any access".

Please think of the counterexamples for which this structure
may not work.
Ankur-0429 pushed a commit to Ankur-0429/llvm-project that referenced this pull request May 9, 2025
This patch produces the following TBAA tree for a function:
```
  Function root
  |
  "any access"
  |
  |- "descriptor member"
  |- "any data access"
     |
     |- "dummy arg data"
     |- "target data"
        |
        |- "allocated data"
        |- "direct data"
        |- "global data"
```
The TBAA tags are assigned using the following logic:
  * All POINTER variables point to the root of "target data".
  * Dummy arguments without POINTER/TARGET point to their
    leafs under "dummy arg data".
  * Dummy arguments with TARGET point to the root of "target data".
  * Global variables without descriptors point to their leafs under
    "global data" (including the ones with TARGET).
  * Global variables with descriptors point to their leafs under
    "direct data" (including the ones with TARGET).
  * Locally allocated variables point to their leafs under
   "allocated data" (including the ones with TARGET).

This change makes it possible to disambiguate globals like:
```
  module data
    real, allocatable :: a(:)
    real, allocatable, target :: b(:)
  end
```

Indeed, two direct references to global vars cannot alias
even if any/both of them have TARGET attribute.
In addition, the dummy arguments without POINTER/TARGET cannot alias
any other variable even with POINTER/TARGET. This was not expressed
in TBAA before this change.

As before, any "unknown" memory references (such as with Indirect
source, as classified by FIR alias analysis) may alias with
anything, as long as they point to the root of "any access".

Please think of the counterexamples for which this structure
may not work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
flang:fir-hlfir flang Flang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants