Skip to content

Conversation

@KrxGu
Copy link
Contributor

@KrxGu KrxGu commented Oct 28, 2025

Fixes #165184

Problem

When using OpenMP atomic read/write on Fortran complex(8) (16 bytes) or complex(16) (32 bytes), only the real part was being stored/loaded. The imaginary part remained unchanged (typically 0).

Root Cause

In OMPIRBuilder::createAtomicRead() and createAtomicWrite(), the size parameter for __atomic_load/__atomic_store was incorrectly computed from the pointer type instead of the pointee (element) type.

On 64-bit systems, this resulted in only 8 bytes being transferred regardless of the actual struct size.

Solution

Changed both functions to use XElemTy (element type) instead of the pointer type when computing LoadSize. This ensures the full struct is transferred:

  • complex(4): 8 bytes ✓
  • complex(8): 16 bytes ✓ (was 8 before)
  • complex(16): 32 bytes ✓ (was 8 before)

Tests

Added three regression tests:

  • atomic-write-complex.f90 - Verifies IR for atomic write
  • atomic-read-complex.f90 - Verifies IR for atomic read
image
  • issue-165184-atomic-complex8.f90 - Direct reproducer from the issue

@llvmbot llvmbot added flang Flang issues not falling into any other category flang:openmp clang:openmp OpenMP related changes to Clang labels Oct 28, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 28, 2025

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

@llvm/pr-subscribers-flang-openmp

Author: Krish Gupta (KrxGu)

Changes

Fixes #165184

Problem

When using OpenMP atomic read/write on Fortran complex(8) (16 bytes) or complex(16) (32 bytes), only the real part was being stored/loaded. The imaginary part remained unchanged (typically 0).

Root Cause

In OMPIRBuilder::createAtomicRead() and createAtomicWrite(), the size parameter for __atomic_load/__atomic_store was incorrectly computed from the pointer type instead of the pointee (element) type.

On 64-bit systems, this resulted in only 8 bytes being transferred regardless of the actual struct size.

Solution

Changed both functions to use XElemTy (element type) instead of the pointer type when computing LoadSize. This ensures the full struct is transferred:

  • complex(4): 8 bytes ✓
  • complex(8): 16 bytes ✓ (was 8 before)
  • complex(16): 32 bytes ✓ (was 8 before)

Tests

Added three regression tests:


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

4 Files Affected:

  • (added) flang/test/Integration/OpenMP/atomic-read-complex.f90 (+44)
  • (added) flang/test/Integration/OpenMP/atomic-write-complex.f90 (+44)
  • (added) flang/test/Integration/OpenMP/issue-165184-atomic-complex8.f90 (+32)
  • (modified) llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp (+4-6)
diff --git a/flang/test/Integration/OpenMP/atomic-read-complex.f90 b/flang/test/Integration/OpenMP/atomic-read-complex.f90
new file mode 100644
index 0000000000000..53ad7acbfaf49
--- /dev/null
+++ b/flang/test/Integration/OpenMP/atomic-read-complex.f90
@@ -0,0 +1,44 @@
+!===----------------------------------------------------------------------===!
+! This directory can be used to add Integration tests involving multiple
+! stages of the compiler (for eg. from Fortran to LLVM IR). It should not
+! contain executable tests. We should only add tests here sparingly and only
+! if there is no other way to test. Repeat this message in each test that is
+! added to this directory and sub-directories.
+!===----------------------------------------------------------------------===!
+
+! REQUIRES: x86-registered-target || aarch64-registered-target
+
+! RUN: %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fopenmp %s -o - | FileCheck %s
+! RUN: %flang_fc1 -triple aarch64-unknown-linux-gnu -emit-llvm -fopenmp %s -o - | FileCheck %s
+
+! Test that atomic read operations with complex types emit the correct
+! size to __atomic_load. This is a regression test for issue #165184.
+!
+! For complex(4) (8 bytes total): should call __atomic_load(i64 8, ...)
+! For complex(8) (16 bytes total): should call __atomic_load(i64 16, ...)
+! For complex(16) (32 bytes total): should call __atomic_load(i64 32, ...)
+
+program atomic_read_complex
+  implicit none
+
+  ! Test complex(4) - single precision (8 bytes)
+  complex(4) :: c41, c42
+  ! Test complex(8) - double precision (16 bytes)
+  complex(8) :: c81, c82
+  
+  c42 = (1.0_4, 1.0_4)
+  c82 = (1.0_8, 1.0_8)
+
+  ! CHECK-LABEL: define {{.*}} @_QQmain
+
+  ! Single precision complex: 8 bytes
+  ! CHECK: call void @__atomic_load(i64 8, ptr {{.*}}, ptr {{.*}}, i32 {{.*}})
+!$omp atomic read
+  c41 = c42
+  
+  ! Double precision complex: 16 bytes (this was broken before the fix)
+  ! CHECK: call void @__atomic_load(i64 16, ptr {{.*}}, ptr {{.*}}, i32 {{.*}})
+!$omp atomic read
+  c81 = c82
+
+end program atomic_read_complex
diff --git a/flang/test/Integration/OpenMP/atomic-write-complex.f90 b/flang/test/Integration/OpenMP/atomic-write-complex.f90
new file mode 100644
index 0000000000000..7302328fba3bf
--- /dev/null
+++ b/flang/test/Integration/OpenMP/atomic-write-complex.f90
@@ -0,0 +1,44 @@
+!===----------------------------------------------------------------------===!
+! This directory can be used to add Integration tests involving multiple
+! stages of the compiler (for eg. from Fortran to LLVM IR). It should not
+! contain executable tests. We should only add tests here sparingly and only
+! if there is no other way to test. Repeat this message in each test that is
+! added to this directory and sub-directories.
+!===----------------------------------------------------------------------===!
+
+! REQUIRES: x86-registered-target || aarch64-registered-target
+
+! RUN: %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fopenmp %s -o - | FileCheck %s
+! RUN: %flang_fc1 -triple aarch64-unknown-linux-gnu -emit-llvm -fopenmp %s -o - | FileCheck %s
+
+! Test that atomic write operations with complex types emit the correct
+! size to __atomic_store. This is a regression test for issue #165184.
+!
+! For complex(4) (8 bytes total): should call __atomic_store(i64 8, ...)
+! For complex(8) (16 bytes total): should call __atomic_store(i64 16, ...)
+! For complex(16) (32 bytes total): should call __atomic_store(i64 32, ...)
+
+program atomic_write_complex
+  implicit none
+
+  ! Test complex(4) - single precision (8 bytes)
+  complex(4) :: c41, c42
+  ! Test complex(8) - double precision (16 bytes)  
+  complex(8) :: c81, c82
+  
+  c42 = (1.0_4, 1.0_4)
+  c82 = (1.0_8, 1.0_8)
+
+  ! CHECK-LABEL: define {{.*}} @_QQmain
+  
+  ! Single precision complex: 8 bytes
+  ! CHECK: call void @__atomic_store(i64 8, ptr {{.*}}, ptr {{.*}}, i32 {{.*}})
+!$omp atomic write
+  c41 = c42
+  
+  ! Double precision complex: 16 bytes (this was broken before the fix)
+  ! CHECK: call void @__atomic_store(i64 16, ptr {{.*}}, ptr {{.*}}, i32 {{.*}})
+!$omp atomic write
+  c81 = c82
+
+end program atomic_write_complex
diff --git a/flang/test/Integration/OpenMP/issue-165184-atomic-complex8.f90 b/flang/test/Integration/OpenMP/issue-165184-atomic-complex8.f90
new file mode 100644
index 0000000000000..8c930191ad233
--- /dev/null
+++ b/flang/test/Integration/OpenMP/issue-165184-atomic-complex8.f90
@@ -0,0 +1,32 @@
+! RUN: %flang_fc1 -emit-llvm -fopenmp -o - %s | FileCheck %s
+! RUN: %flang -fopenmp %s -o %t && %t | FileCheck %s --check-prefix=EXEC
+
+! Regression test for issue #165184:
+! Atomic write to complex(8) was only storing 8 bytes instead of 16,
+! causing the imaginary part to remain 0.
+
+program test_atomic_write_complex8
+  implicit none
+  complex(8) :: c81, c82
+  
+  c81 = (0.0_8, 0.0_8)
+  c82 = (0.0_8, 0.0_8)
+  
+  ! Verify the fix: __atomic_store should be called with size 16
+  ! CHECK: call void @__atomic_store(i64 16,
+  
+!$omp parallel
+!$omp atomic write
+  c81 = c82 + (1.0_8, 1.0_8)
+!$omp end parallel
+  
+  ! EXEC: c81 = (1.00000000000000,1.00000000000000)
+  write(*,*) "c81 = ", c81
+  
+  ! Verify both parts are correct
+  if (real(c81) /= 1.0_8 .or. aimag(c81) /= 1.0_8) then
+    write(*,*) "ERROR: Expected (1.0,1.0) but got", c81
+    stop 1
+  end if
+  
+end program test_atomic_write_complex8
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index 286ed039b1214..c71b4a1b6eaa1 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -9338,9 +9338,8 @@ OpenMPIRBuilder::createAtomicRead(const LocationDescription &Loc,
     // target does not support `atomicrmw` of the size of the struct
     LoadInst *OldVal = Builder.CreateLoad(XElemTy, X.Var, "omp.atomic.read");
     OldVal->setAtomic(AO);
-    const DataLayout &LoadDL = OldVal->getModule()->getDataLayout();
-    unsigned LoadSize =
-        LoadDL.getTypeStoreSize(OldVal->getPointerOperand()->getType());
+    const DataLayout &DL = OldVal->getModule()->getDataLayout();
+    unsigned LoadSize = DL.getTypeStoreSize(XElemTy);
     OpenMPIRBuilder::AtomicInfo atomicInfo(
         &Builder, XElemTy, LoadSize * 8, LoadSize * 8, OldVal->getAlign(),
         OldVal->getAlign(), true /* UseLibcall */, AllocaIP, X.Var);
@@ -9384,9 +9383,8 @@ OpenMPIRBuilder::createAtomicWrite(const LocationDescription &Loc,
     XSt->setAtomic(AO);
   } else if (XElemTy->isStructTy()) {
     LoadInst *OldVal = Builder.CreateLoad(XElemTy, X.Var, "omp.atomic.read");
-    const DataLayout &LoadDL = OldVal->getModule()->getDataLayout();
-    unsigned LoadSize =
-        LoadDL.getTypeStoreSize(OldVal->getPointerOperand()->getType());
+    const DataLayout &DL = OldVal->getModule()->getDataLayout();
+    unsigned LoadSize = DL.getTypeStoreSize(XElemTy);
     OpenMPIRBuilder::AtomicInfo atomicInfo(
         &Builder, XElemTy, LoadSize * 8, LoadSize * 8, OldVal->getAlign(),
         OldVal->getAlign(), true /* UseLibcall */, AllocaIP, X.Var);

@KrxGu KrxGu force-pushed the fix-issue-165184-atomic-complex branch from e536d92 to b0b04fd Compare October 28, 2025 11:42
@KrxGu KrxGu force-pushed the fix-issue-165184-atomic-complex branch from b0b04fd to e820cd5 Compare October 28, 2025 12:49
@KrxGu
Copy link
Contributor Author

KrxGu commented Oct 28, 2025

@kparzysz or @tblah Can one of you please review this fix for #165184?

The issue was that atomic operations on complex(8) and larger complex types were only transferring 8 bytes (pointer size) instead of the full struct size, causing the imaginary part to be lost.

The fix changes the size calculation in OMPIRBuilder::createAtomicRead/Write() to use the element type instead of the pointer type. I've added regression tests and all CI checks are passing.

Thanks in advance for your time! CI all green

Copy link
Contributor

@kparzysz kparzysz left a comment

Choose a reason for hiding this comment

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

LGTM

@kparzysz
Copy link
Contributor

In the actual commit, please only put a summary of the PR, for example

In OMPIRBuilder::createAtomicRead() and createAtomicWrite(), the size parameter for __atomic_load/__atomic_store was incorrectly computed from the pointer type instead of the pointee (element) type.

On 64-bit systems, this resulted in only 8 bytes being transferred regardless of the actual struct size.

Changed both functions to use XElemTy (element type) instead of the pointer type when computing LoadSize. This ensures the full struct is transferred.

Fixes llvm#165184

In OMPIRBuilder::createAtomicRead() and createAtomicWrite(), the size
parameter for __atomic_load/__atomic_store was incorrectly computed
from the pointer type instead of the pointee (element) type.

On 64-bit systems, this resulted in only 8 bytes being transferred
regardless of the actual struct size.

Changed both functions to use XElemTy (element type) instead of the
pointer type when computing LoadSize. This ensures the full struct
is transferred.
@KrxGu KrxGu force-pushed the fix-issue-165184-atomic-complex branch from e820cd5 to 5524ecd Compare October 28, 2025 14:23
@kiranchandramohan
Copy link
Contributor

If the fix is in OpenMPIRBuilder, it is probably best to have the test also close to it.
llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
mlir/test/Target/LLVMIR/openmp-llvm.mlir

@KrxGu
Copy link
Contributor Author

KrxGu commented Oct 28, 2025

@kparzysz Commit message updated and squashed per review. CI all green!!

@kiranchandramohan Re: colocating tests with the fix in OpenMPIRBuilder, plan is:

  • Add a gtest in llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp that builds atomic write/read on {double,double} and asserts the first arg to __atomic_{load,store} is 16.
  • Add an MLIR translation check in mlir/test/Target/LLVMIR/openmp-llvm.mlir for omp.atomic lowering to __atomic_* with i64 16.

I can keep the Flang IR tests as frontend coverage, or drop them if you prefer only the two tests above. Please advise.

@kparzysz Does this test plan align with your expectations? and are we on the same page?

@kparzysz
Copy link
Contributor

You can just add the additional tests to this PR.

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.

Thank you for the bug fix!

@github-actions
Copy link

github-actions bot commented Oct 28, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

OpenMP atomic operations on complex(8) were using pointer size (8 bytes)
instead of the actual element size (16 bytes), causing only 8 bytes to
be stored/loaded instead of the full 16 bytes.

Fixed by using DL.getTypeStoreSize(XElemTy) instead of pointer type size
in createAtomicRead and createAtomicWrite for both atomic operations.

Added Flang IR tests to verify correct size generation for complex(4)
and complex(8) atomic operations, and unit test to verify struct types
with __atomic_store/__atomic_load.

Fixes llvm#165184
@KrxGu KrxGu force-pushed the fix-issue-165184-atomic-complex branch from 1b45069 to 81e5343 Compare October 29, 2025 05:14
@KrxGu
Copy link
Contributor Author

KrxGu commented Oct 29, 2025

@kiranchandramohan , @kparzysz - I've addressed the feedback and made the requested changes. The PR now has:

Unit test for atomic operations on struct types (test OMPAtomicRWStructType in OpenMPIRBuilderTest.cpp)

  • Verifies that __atomic_store and __atomic_load are called with the correct size (16 bytes) for {double, double} struct type
  • Confirms the fix works for struct types similar to complex(8)

The commit history has been squashed into a single clean commit with a descriptive message. Please review when you get a chance. CI is all green, Good to Merge!!

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.

LGTM, thanks!

@kparzysz kparzysz merged commit 22a10c8 into llvm:main Oct 29, 2025
10 checks passed
aokblast pushed a commit to aokblast/llvm-project that referenced this pull request Oct 30, 2025
Fixes llvm#165184

In OMPIRBuilder::createAtomicRead() and createAtomicWrite(), the size
parameter for __atomic_load/__atomic_store was incorrectly computed
from the pointer type instead of the pointee (element) type.

On 64-bit systems, this resulted in only 8 bytes being transferred
regardless of the actual struct size.

Changed both functions to use XElemTy (element type) instead of the
pointer type when computing LoadSize. This ensures the full struct
is transferred.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:openmp OpenMP related changes to Clang flang:fir-hlfir flang:openmp flang Flang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Flang][OpenMP] Incorrect execution result when using double precision complex in a statement within ATOMIC WRITE construct

5 participants