Skip to content

Conversation

tblah
Copy link
Contributor

@tblah tblah commented Jul 2, 2025

This is not allowed by the openmp standard.

This is not allowed by the openmp standard.
@llvmbot
Copy link
Member

llvmbot commented Jul 2, 2025

@llvm/pr-subscribers-mlir-llvm
@llvm/pr-subscribers-mlir

@llvm/pr-subscribers-mlir-openmp

Author: Tom Eccles (tblah)

Changes

This is not allowed by the openmp standard.


Full diff: https://github.com/llvm/llvm-project/pull/146734.diff

2 Files Affected:

  • (modified) mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp (+22-1)
  • (modified) mlir/test/Target/LLVMIR/openmp-todo.mlir (+23)
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 7b07243c5f843..b307de09fbcc4 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -326,6 +326,25 @@ static LogicalResult checkImplementationStatus(Operation &op) {
     if (op.getDistScheduleChunkSize())
       result = todo("dist_schedule with chunk_size");
   };
+  auto checkFirstprivate = [&todo](auto op, LogicalResult &result) {
+    // Firstprivate is not listed as supported by the simd operation in
+    // OpenMP 6.0. This is here to catch it because it is allowed at the dialect
+    // level.
+    if constexpr (std::is_same_v<std::decay_t<decltype(op)>, omp::SimdOp>) {
+      std::optional<ArrayAttr> privateSyms = op.getPrivateSyms();
+      if (!privateSyms)
+        return;
+      for (const Attribute &sym : *privateSyms) {
+        omp::PrivateClauseOp privatizer =
+            findPrivatizer(op, cast<SymbolRefAttr>(sym));
+        if (privatizer.getDataSharingType() ==
+            omp::DataSharingClauseType::FirstPrivate) {
+          result = todo("firstprivate");
+          break;
+        }
+      }
+    }
+  };
   auto checkHint = [](auto op, LogicalResult &) {
     if (op.getHint())
       op.emitWarning("hint clause discarded");
@@ -441,6 +460,7 @@ static LogicalResult checkImplementationStatus(Operation &op) {
         checkReduction(op, result);
       })
       .Case([&](omp::SimdOp op) {
+        checkFirstprivate(op, result);
         checkLinear(op, result);
         checkReduction(op, result);
       })
@@ -2894,7 +2914,8 @@ convertOmpSimd(Operation &opInst, llvm::IRBuilderBase &builder,
           .failed())
     return failure();
 
-  // TODO: no call to copyFirstPrivateVars?
+  // No call to copyFirstPrivateVars because firstprivate is not allowed on
+  // SIMD.
 
   assert(afterAllocas.get()->getSinglePredecessor());
   if (failed(initReductionVars(simdOp, reductionArgs, builder,
diff --git a/mlir/test/Target/LLVMIR/openmp-todo.mlir b/mlir/test/Target/LLVMIR/openmp-todo.mlir
index 29725a02c075a..e86a91dab873d 100644
--- a/mlir/test/Target/LLVMIR/openmp-todo.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-todo.mlir
@@ -503,3 +503,26 @@ llvm.func @wsloop_order(%lb : i32, %ub : i32, %step : i32) {
   }
   llvm.return
 }
+
+// -----
+
+omp.private {type = firstprivate} @_QFEp_firstprivate_i32 : i32 copy {
+^bb0(%arg0: !llvm.ptr, %arg1: !llvm.ptr):
+  %0 = llvm.load %arg0 : !llvm.ptr -> i32
+  llvm.store %0, %arg1 : i32, !llvm.ptr
+  omp.yield(%arg1 : !llvm.ptr)
+}
+llvm.func @_QQmain() {
+  %0 = llvm.mlir.constant(1 : i64) : i64
+  %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr
+  %3 = llvm.mlir.constant(10 : i32) : i32
+  %4 = llvm.mlir.constant(1 : i32) : i32
+  // expected-error@below {{not yet implemented: Unhandled clause firstprivate in omp.simd operation}}
+  // expected-error@below {{LLVM Translation failed for operation: omp.simd}}
+  omp.simd private(@_QFEp_firstprivate_i32 %1 -> %arg0 : !llvm.ptr) {
+    omp.loop_nest (%arg2) : i32 = (%4) to (%3) inclusive step (%4) {
+      omp.yield
+    }
+  }
+  llvm.return
+}

@llvmbot
Copy link
Member

llvmbot commented Jul 2, 2025

@llvm/pr-subscribers-flang-openmp

Author: Tom Eccles (tblah)

Changes

This is not allowed by the openmp standard.


Full diff: https://github.com/llvm/llvm-project/pull/146734.diff

2 Files Affected:

  • (modified) mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp (+22-1)
  • (modified) mlir/test/Target/LLVMIR/openmp-todo.mlir (+23)
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 7b07243c5f843..b307de09fbcc4 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -326,6 +326,25 @@ static LogicalResult checkImplementationStatus(Operation &op) {
     if (op.getDistScheduleChunkSize())
       result = todo("dist_schedule with chunk_size");
   };
+  auto checkFirstprivate = [&todo](auto op, LogicalResult &result) {
+    // Firstprivate is not listed as supported by the simd operation in
+    // OpenMP 6.0. This is here to catch it because it is allowed at the dialect
+    // level.
+    if constexpr (std::is_same_v<std::decay_t<decltype(op)>, omp::SimdOp>) {
+      std::optional<ArrayAttr> privateSyms = op.getPrivateSyms();
+      if (!privateSyms)
+        return;
+      for (const Attribute &sym : *privateSyms) {
+        omp::PrivateClauseOp privatizer =
+            findPrivatizer(op, cast<SymbolRefAttr>(sym));
+        if (privatizer.getDataSharingType() ==
+            omp::DataSharingClauseType::FirstPrivate) {
+          result = todo("firstprivate");
+          break;
+        }
+      }
+    }
+  };
   auto checkHint = [](auto op, LogicalResult &) {
     if (op.getHint())
       op.emitWarning("hint clause discarded");
@@ -441,6 +460,7 @@ static LogicalResult checkImplementationStatus(Operation &op) {
         checkReduction(op, result);
       })
       .Case([&](omp::SimdOp op) {
+        checkFirstprivate(op, result);
         checkLinear(op, result);
         checkReduction(op, result);
       })
@@ -2894,7 +2914,8 @@ convertOmpSimd(Operation &opInst, llvm::IRBuilderBase &builder,
           .failed())
     return failure();
 
-  // TODO: no call to copyFirstPrivateVars?
+  // No call to copyFirstPrivateVars because firstprivate is not allowed on
+  // SIMD.
 
   assert(afterAllocas.get()->getSinglePredecessor());
   if (failed(initReductionVars(simdOp, reductionArgs, builder,
diff --git a/mlir/test/Target/LLVMIR/openmp-todo.mlir b/mlir/test/Target/LLVMIR/openmp-todo.mlir
index 29725a02c075a..e86a91dab873d 100644
--- a/mlir/test/Target/LLVMIR/openmp-todo.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-todo.mlir
@@ -503,3 +503,26 @@ llvm.func @wsloop_order(%lb : i32, %ub : i32, %step : i32) {
   }
   llvm.return
 }
+
+// -----
+
+omp.private {type = firstprivate} @_QFEp_firstprivate_i32 : i32 copy {
+^bb0(%arg0: !llvm.ptr, %arg1: !llvm.ptr):
+  %0 = llvm.load %arg0 : !llvm.ptr -> i32
+  llvm.store %0, %arg1 : i32, !llvm.ptr
+  omp.yield(%arg1 : !llvm.ptr)
+}
+llvm.func @_QQmain() {
+  %0 = llvm.mlir.constant(1 : i64) : i64
+  %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr
+  %3 = llvm.mlir.constant(10 : i32) : i32
+  %4 = llvm.mlir.constant(1 : i32) : i32
+  // expected-error@below {{not yet implemented: Unhandled clause firstprivate in omp.simd operation}}
+  // expected-error@below {{LLVM Translation failed for operation: omp.simd}}
+  omp.simd private(@_QFEp_firstprivate_i32 %1 -> %arg0 : !llvm.ptr) {
+    omp.loop_nest (%arg2) : i32 = (%4) to (%3) inclusive step (%4) {
+      omp.yield
+    }
+  }
+  llvm.return
+}

Copy link
Member

@skatrak skatrak 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 looking into this, I've got just one comment.

if (op.getDistScheduleChunkSize())
result = todo("dist_schedule with chunk_size");
};
auto checkFirstprivate = [&todo](auto op, LogicalResult &result) {
Copy link
Member

Choose a reason for hiding this comment

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

Rather than checking this here, perhaps the MLIR op verifier (SimdOp::verify) would be a better place to do it. Otherwise, if a frontend produced an invalid MLIR where omp.simd took a firstprivate privatizer argument, it would be reported as not-yet-implemented when it's actually an invalid condition.

Copy link
Member

@skatrak skatrak left a comment

Choose a reason for hiding this comment

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

Thank you, this LGTM!


// TODO: no call to copyFirstPrivateVars?
// No call to copyFirstPrivateVars because FIRSTPRIVATE is not allowed for
// SIMD
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// SIMD
// SIMD.

@tblah tblah merged commit ea5ee2e into llvm:main Jul 4, 2025
4 of 7 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Jul 4, 2025

LLVM Buildbot has detected a new failure on builder premerge-monolithic-linux running on premerge-linux-1 while building mlir at step 7 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/153/builds/36959

Here is the relevant piece of the build log for the reference
Step 7 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'Flang :: Fir/convert-nontemporal-to-llvm.fir' FAILED ********************
Exit Code: 2

Command Output (stderr):
--
fir-opt --fir-to-llvm-ir /build/buildbot/premerge-monolithic-linux/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir | /build/buildbot/premerge-monolithic-linux/build/bin/FileCheck /build/buildbot/premerge-monolithic-linux/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir --check-prefixes=CHECK-LABEL,CHECK # RUN: at line 2
+ fir-opt --fir-to-llvm-ir /build/buildbot/premerge-monolithic-linux/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir
+ /build/buildbot/premerge-monolithic-linux/build/bin/FileCheck /build/buildbot/premerge-monolithic-linux/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir --check-prefixes=CHECK-LABEL,CHECK
/build/buildbot/premerge-monolithic-linux/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir:36:5: error: Cannot find privatizer '@_QFtestEi_private_i32'
    omp.simd nontemporal(%0, %2 : !fir.ref<i32>, !fir.ref<i32>) private(@_QFtestEi_private_i32 %3 -> %arg0 : !fir.ref<i32>) {
    ^
/build/buildbot/premerge-monolithic-linux/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir:36:5: note: see current operation: 
"omp.simd"(%1, %3, %4) <{operandSegmentSizes = array<i32: 0, 0, 0, 0, 2, 1, 0>, private_syms = [@_QFtestEi_private_i32]}> ({
^bb0(%arg0: !fir.ref<i32>):
  "omp.loop_nest"(%0, %6, %0) <{loop_inclusive}> ({
  ^bb0(%arg1: i32):
    "fir.store"(%arg1, %arg0) : (i32, !fir.ref<i32>) -> ()
    %7 = "fir.load"(%1) <{nontemporal}> : (!fir.ref<i32>) -> i32
    %8 = "fir.load"(%2) : (!fir.ref<i32>) -> i32
    %9 = "arith.addi"(%7, %8) <{overflowFlags = #arith.overflow<none>}> : (i32, i32) -> i32
    "fir.store"(%9, %3) <{nontemporal}> : (i32, !fir.ref<i32>) -> ()
    "omp.yield"() : () -> ()
  }) : (i32, i32, i32) -> ()
}) : (!fir.ref<i32>, !fir.ref<i32>, !fir.ref<i32>) -> ()
/build/buildbot/premerge-monolithic-linux/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir:94:5: error: Cannot find privatizer '@_QFsimd_nontemporal_allocatableEi_private_i32'
    omp.simd nontemporal(%arg0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>) private(@_QFsimd_nontemporal_allocatableEi_private_i32 %0 -> %arg2 : !fir.ref<i32>) {
    ^
/build/buildbot/premerge-monolithic-linux/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir:94:5: note: see current operation: 
"omp.simd"(%arg0, %4) <{operandSegmentSizes = array<i32: 0, 0, 0, 0, 1, 1, 0>, private_syms = [@_QFsimd_nontemporal_allocatableEi_private_i32]}> ({
^bb0(%arg2: !fir.ref<i32>):
  "omp.loop_nest"(%1, %3, %1) <{loop_inclusive}> ({
  ^bb0(%arg3: i32):
    "fir.store"(%arg3, %arg2) : (i32, !fir.ref<i32>) -> ()
    %7 = "fir.load"(%arg0) : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>) -> !fir.box<!fir.heap<!fir.array<?xi32>>>
    %8 = "fir.load"(%arg2) : (!fir.ref<i32>) -> i32
    %9 = "fir.convert"(%8) : (i32) -> i64
    %10 = "fir.box_addr"(%7) : (!fir.box<!fir.heap<!fir.array<?xi32>>>) -> !fir.heap<!fir.array<?xi32>>
    %11:3 = "fir.box_dims"(%7, %2) : (!fir.box<!fir.heap<!fir.array<?xi32>>>, index) -> (index, index, index)
    %12 = "fircg.ext_array_coor"(%10, %11#1, %11#0, %9) <{operandSegmentSizes = array<i32: 1, 1, 1, 0, 0, 1, 0>}> : (!fir.heap<!fir.array<?xi32>>, index, index, i64) -> !fir.ref<i32>
    %13 = "fir.load"(%12) <{nontemporal}> : (!fir.ref<i32>) -> i32
    %14 = "fir.load"(%arg1) : (!fir.ref<i32>) -> i32
    %15 = "arith.addi"(%13, %14) <{overflowFlags = #arith.overflow<none>}> : (i32, i32) -> i32
    "fir.store"(%15, %12) <{nontemporal}> : (i32, !fir.ref<i32>) -> ()
    "omp.yield"() : () -> ()
  }) : (i32, i32, i32) -> ()
}) : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, !fir.ref<i32>) -> ()
FileCheck error: '<stdin>' is empty.
FileCheck command line:  /build/buildbot/premerge-monolithic-linux/build/bin/FileCheck /build/buildbot/premerge-monolithic-linux/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir --check-prefixes=CHECK-LABEL,CHECK

--
...

@llvm-ci
Copy link
Collaborator

llvm-ci commented Jul 4, 2025

LLVM Buildbot has detected a new failure on builder ppc64le-flang-rhel-clang running on ppc64le-flang-rhel-test while building mlir at step 6 "test-build-unified-tree-check-flang".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/157/builds/32743

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-flang) failure: test (failure)
******************** TEST 'Flang :: Fir/convert-nontemporal-to-llvm.fir' FAILED ********************
Exit Code: 2

Command Output (stderr):
--
fir-opt --fir-to-llvm-ir /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir | /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir --check-prefixes=CHECK-LABEL,CHECK # RUN: at line 2
+ fir-opt --fir-to-llvm-ir /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir
+ /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir --check-prefixes=CHECK-LABEL,CHECK
/home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir:36:5: error: Cannot find privatizer '@_QFtestEi_private_i32'
    omp.simd nontemporal(%0, %2 : !fir.ref<i32>, !fir.ref<i32>) private(@_QFtestEi_private_i32 %3 -> %arg0 : !fir.ref<i32>) {
    ^
/home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir:36:5: note: see current operation: 
"omp.simd"(%1, %3, %4) <{operandSegmentSizes = array<i32: 0, 0, 0, 0, 2, 1, 0>, private_syms = [@_QFtestEi_private_i32]}> ({
^bb0(%arg0: !fir.ref<i32>):
  "omp.loop_nest"(%0, %6, %0) <{loop_inclusive}> ({
  ^bb0(%arg1: i32):
    "fir.store"(%arg1, %arg0) : (i32, !fir.ref<i32>) -> ()
    %7 = "fir.load"(%1) <{nontemporal}> : (!fir.ref<i32>) -> i32
    %8 = "fir.load"(%2) : (!fir.ref<i32>) -> i32
    %9 = "arith.addi"(%7, %8) <{overflowFlags = #arith.overflow<none>}> : (i32, i32) -> i32
    "fir.store"(%9, %3) <{nontemporal}> : (i32, !fir.ref<i32>) -> ()
    "omp.yield"() : () -> ()
  }) : (i32, i32, i32) -> ()
}) : (!fir.ref<i32>, !fir.ref<i32>, !fir.ref<i32>) -> ()
/home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir:94:5: error: Cannot find privatizer '@_QFsimd_nontemporal_allocatableEi_private_i32'
    omp.simd nontemporal(%arg0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>) private(@_QFsimd_nontemporal_allocatableEi_private_i32 %0 -> %arg2 : !fir.ref<i32>) {
    ^
/home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir:94:5: note: see current operation: 
"omp.simd"(%arg0, %4) <{operandSegmentSizes = array<i32: 0, 0, 0, 0, 1, 1, 0>, private_syms = [@_QFsimd_nontemporal_allocatableEi_private_i32]}> ({
^bb0(%arg2: !fir.ref<i32>):
  "omp.loop_nest"(%1, %3, %1) <{loop_inclusive}> ({
  ^bb0(%arg3: i32):
    "fir.store"(%arg3, %arg2) : (i32, !fir.ref<i32>) -> ()
    %7 = "fir.load"(%arg0) : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>) -> !fir.box<!fir.heap<!fir.array<?xi32>>>
    %8 = "fir.load"(%arg2) : (!fir.ref<i32>) -> i32
    %9 = "fir.convert"(%8) : (i32) -> i64
    %10 = "fir.box_addr"(%7) : (!fir.box<!fir.heap<!fir.array<?xi32>>>) -> !fir.heap<!fir.array<?xi32>>
    %11:3 = "fir.box_dims"(%7, %2) : (!fir.box<!fir.heap<!fir.array<?xi32>>>, index) -> (index, index, index)
    %12 = "fircg.ext_array_coor"(%10, %11#1, %11#0, %9) <{operandSegmentSizes = array<i32: 1, 1, 1, 0, 0, 1, 0>}> : (!fir.heap<!fir.array<?xi32>>, index, index, i64) -> !fir.ref<i32>
    %13 = "fir.load"(%12) <{nontemporal}> : (!fir.ref<i32>) -> i32
    %14 = "fir.load"(%arg1) : (!fir.ref<i32>) -> i32
    %15 = "arith.addi"(%13, %14) <{overflowFlags = #arith.overflow<none>}> : (i32, i32) -> i32
    "fir.store"(%15, %12) <{nontemporal}> : (i32, !fir.ref<i32>) -> ()
    "omp.yield"() : () -> ()
  }) : (i32, i32, i32) -> ()
}) : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, !fir.ref<i32>) -> ()
FileCheck error: '<stdin>' is empty.
FileCheck command line:  /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Fir/convert-nontemporal-to-llvm.fir --check-prefixes=CHECK-LABEL,CHECK

--
...

tblah added a commit to tblah/llvm-project that referenced this pull request Jul 4, 2025
These tests referred to privatizers which were never declared
tblah added a commit that referenced this pull request Jul 4, 2025
These tests referred to privatizers which were never declared
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants