[Flang][OpenMP] Support mapping of zero-sized arrays (#208133)

Whilst there is no data in theory to map with a zero sized array, the
Fortran OpenMP specification considers the case of a zero sized array
being mapped to device as the data being present on device. This
includes both the data and the descriptor, and it should fundamentlaly
work as a zero-sized array would on device with the respective Fortran
functions for presence and size checking etc.

We can't actually map a nullptr to device and expect it to be present in
current OpenMP (for good reason), however, thankfully, a zero sized
array isn't actually a nullptr, it is a descriptor contianing an
allocated 1-byte of data. So, we can map this to device, alongside the
descriptor and then the zero sized array is correctlly on device for all
intents and purposes. This case is notably different from a
non-allocated or non-zero sized array so we can do this without shooting
ourselves in the foot.
This commit is contained in:
agozillon 2026-07-11 01:03:33 +02:00 committed by GitHub
parent 5a73020f02
commit 7e89dcfba2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 128 additions and 45 deletions

View file

@ -27,6 +27,7 @@
#include "flang/Optimizer/Builder/DirectivesCommon.h"
#include "flang/Optimizer/Builder/FIRBuilder.h"
#include "flang/Optimizer/Builder/HLFIRTools.h"
#include "flang/Optimizer/Builder/MutableBox.h"
#include "flang/Optimizer/Dialect/FIRType.h"
#include "flang/Optimizer/Dialect/Support/KindMapping.h"
#include "flang/Optimizer/HLFIR/HLFIROps.h"
@ -375,21 +376,21 @@ class MapInfoFinalizationPass
builder.restoreInsertionPoint(insPt);
}
// We should only emit a store if the passed in data is present, it is
// possible a user passes in no argument to an optional parameter, in which
// case we cannot store or we'll segfault on the emitted memcpy.
// TODO: We currently emit a present -> load/store every time we use a
// mapped value that requires a local allocation, this isn't the most
// efficient, although, it is more correct in a lot of situations. One
// such situation is emitting a this series of instructions in separate
// segments of a branch (e.g. two target regions in separate else/if branch
// mapping the same function argument), however, it would be nice to be able
// to optimize these situations e.g. raising the load/store out of the
// branch if possible. But perhaps this is best left to lower level
// optimisation passes.
// Make sure that our new allocation is "allocated" to default box state.
mlir::Type boxType = fir::unwrapRefType(alloca.getType());
mlir::Value nullBox = fir::factory::createUnallocatedBox(
builder, loc, boxType, /*nonDeferredLenParams=*/{});
fir::StoreOp::create(builder, loc, nullBox, alloca);
// Only overwrite with actual descriptor data if present.
// TODO: We currently emit a present check every time we use a mapped
// value that requires a local allocation. This isn't the most efficient,
// but it is correct for situations like separate target regions in
// different branches mapping the same function argument. Optimization
// (e.g., hoisting the load/store) is best left to lower level passes.
auto isPresent =
fir::IsPresentOp::create(builder, loc, builder.getI1Type(), descriptor);
builder.genIfOp(loc, {}, isPresent, false)
builder.genIfOp(loc, {}, isPresent, /*withElseRegion=*/false)
.genThen([&]() {
descriptor = builder.loadIfRef(loc, descriptor);
fir::StoreOp::create(builder, loc, descriptor, alloca);
@ -760,7 +761,8 @@ class MapInfoFinalizationPass
return mapTypeFlag;
}
flags |= MapFlags::to | (mapTypeFlag & MapFlags::implicit);
flags |=
MapFlags::to | (mapTypeFlag & (MapFlags::implicit | MapFlags::present));
// Descriptors for objects will always be copied. This is because the
// descriptor can be rematerialized by the compiler, and so the address

View file

@ -615,6 +615,8 @@ end subroutine mapType_common_block_members
!CHECK: %[[MEMBER_SIZE_CALC_2:.*]] = add i64 %[[MEMBER_SIZE_CALC_1]], 1
!CHECK: %[[MEMBER_SIZE_CALC_3:.*]] = mul i64 1, %[[MEMBER_SIZE_CALC_2]]
!CHECK: %[[MEMBER_SIZE_CALC_4:.*]] = mul i64 %[[MEMBER_SIZE_CALC_3]], 4
!CHECK: %[[SIZE_ZERO_CMP:.*]] = icmp eq i64 %[[MEMBER_SIZE_CALC_4]], 0
!CHECK: %[[SIZE_ADJUSTED:.*]] = select i1 %[[SIZE_ZERO_CMP]], i64 1, i64 %[[MEMBER_SIZE_CALC_4]]
!CHECK: %[[DTYPE_BASE_ADDR_LOAD_3:.*]] = load ptr, ptr %[[DTYPE_BASE_ADDR_ACCESS_3]], align 8
!CHECK: %[[DTYPE_BASE_ADDR_LOAD_3_1:.*]] = load ptr, ptr %[[DTYPE_BASE_ADDR_ACCESS_3]], align 8
!CHECK: %[[LOAD_DTYPE_DESC_MEMBER:.*]] = load ptr, ptr %[[DTYPE_ALLOCA_MEMBER_BASE_ADDR_ACCESS]], align 8
@ -626,7 +628,7 @@ end subroutine mapType_common_block_members
!CHECK: %[[NULL_CMP:.*]] = icmp eq ptr %[[DTYPE_BASE_ADDR_LOAD_3_1]], null
!CHECK: %[[SEL_SZ:.*]] = select i1 %[[NULL_CMP]], i64 0, i64 136
!CHECK: %[[NULL_CMP2:.*]] = icmp eq ptr %{{.*}}, null
!CHECK: %[[SEL_SZ2:.*]] = select i1 %[[NULL_CMP2]], i64 0, i64 %[[MEMBER_SIZE_CALC_4]]
!CHECK: %[[SEL_SZ2:.*]] = select i1 %[[NULL_CMP2]], i64 0, i64 %[[SIZE_ADJUSTED]]
!CHECK: getelementptr inbounds [9 x ptr], ptr %.offload_baseptrs, i32 0, i32 0
!CHECK: getelementptr inbounds [9 x ptr], ptr %.offload_ptrs, i32 0, i32 0
!CHECK: getelementptr inbounds [9 x ptr], ptr %.offload_baseptrs, i32 0, i32 1
@ -663,6 +665,8 @@ end subroutine mapType_common_block_members
!CHECK: %[[ALLOCATABLE_MEMBER_SIZE_CALC_3:.*]] = add i64 %[[ALLOCATABLE_MEMBER_SIZE_CALC_2]], 1
!CHECK: %[[ALLOCATABLE_MEMBER_SIZE_CALC_4:.*]] = mul i64 1, %[[ALLOCATABLE_MEMBER_SIZE_CALC_3]]
!CHECK: %[[ALLOCATABLE_MEMBER_SIZE_CALC_5:.*]] = mul i64 %[[ALLOCATABLE_MEMBER_SIZE_CALC_4]], 4
!CHECK: %[[SIZE_ZERO_CMP:.*]] = icmp eq i64 %[[ALLOCATABLE_MEMBER_SIZE_CALC_5]], 0
!CHECK: %[[SIZE_ADJUSTED:.*]] = select i1 %[[SIZE_ZERO_CMP]], i64 1, i64 %[[ALLOCATABLE_MEMBER_SIZE_CALC_5]]
!CHECK: %[[LOAD_BASE_ADDR:.*]] = load ptr, ptr %[[DTYPE_DESC_BASE_ADDR]], align 8
!CHECK: %[[LOAD_BASE_ADDR2:.*]] = load ptr, ptr %[[DTYPE_DESC_BASE_ADDR]], align 8
!CHECK: %[[LOAD_DESC_MEMBER_BASE_ADDR:.*]] = load ptr, ptr %[[MAPPED_MEMBER_BASE_ADDR_ACCESS]], align 8
@ -670,7 +674,7 @@ end subroutine mapType_common_block_members
!CHECK: %[[NULL_CMP:.*]] = icmp eq ptr %[[LOAD_BASE_ADDR2]], null
!CHECK: %[[SEL_SZ:.*]] = select i1 %[[NULL_CMP]], i64 0, i64 240
!CHECK: %[[NULL_CMP2:.*]] = icmp eq ptr %[[ARRAY_OFFSET]], null
!CHECK: %[[SEL_SZ2:.*]] = select i1 %[[NULL_CMP2]], i64 0, i64 %[[ALLOCATABLE_MEMBER_SIZE_CALC_5]]
!CHECK: %[[SEL_SZ2:.*]] = select i1 %[[NULL_CMP2]], i64 0, i64 %[[SIZE_ADJUSTED]]
!CHECK: %[[BASE_PTR_ARR:.*]] = getelementptr inbounds [9 x ptr], ptr %.offload_baseptrs, i32 0, i32 0
!CHECK: store ptr %[[DTYPE_DESC_ALLOCA_3]], ptr %[[BASE_PTR_ARR]], align 8
!CHECK: %[[OFFLOAD_PTR_ARR:.*]] = getelementptr inbounds [9 x ptr], ptr %.offload_ptrs, i32 0, i32 0
@ -700,6 +704,8 @@ end subroutine mapType_common_block_members
!CHECK: %[[ALLOCATABLE_MEMBER_SIZE_CALC_3:.*]] = add i64 %[[ALLOCATABLE_MEMBER_SIZE_CALC_2]], 1
!CHECK: %[[ALLOCATABLE_MEMBER_SIZE_CALC_4:.*]] = mul i64 1, %[[ALLOCATABLE_MEMBER_SIZE_CALC_3]]
!CHECK: %[[ALLOCATABLE_MEMBER_SIZE_CALC_5:.*]] = mul i64 %[[ALLOCATABLE_MEMBER_SIZE_CALC_4]], 4
!CHECK: %[[SIZE_ZERO_CMP:.*]] = icmp eq i64 %[[ALLOCATABLE_MEMBER_SIZE_CALC_5]], 0
!CHECK: %[[SIZE_ADJUSTED:.*]] = select i1 %[[SIZE_ZERO_CMP]], i64 1, i64 %[[ALLOCATABLE_MEMBER_SIZE_CALC_5]]
!CHECK: %[[LOAD_BASE_ADDR:.*]] = load ptr, ptr %[[NESTED_MEMBER_BASE_ADDR_ACCESS]], align 8
!CHECK: %[[ARR_OFFS:.*]] = getelementptr inbounds i32, ptr %[[LOAD_BASE_ADDR]], i64 0
!CHECK: %[[LOAD_BASE_ADDR:.*]] = load ptr, ptr %[[NESTED_MEMBER_BASE_ADDR_ACCESS]], align 8
@ -709,7 +715,7 @@ end subroutine mapType_common_block_members
!CHECK: %[[DTYPE_SEGMENT_SIZE_CALC_2:.*]] = ptrtoaddr ptr %[[NESTED_MEMBER_ACCESS]] to i64
!CHECK: %[[DTYPE_SEGMENT_SIZE_CALC_3:.*]] = sub i64 %[[DTYPE_SEGMENT_SIZE_CALC_1]], %[[DTYPE_SEGMENT_SIZE_CALC_2]]
!CHECK: %[[DATA_CMP:.*]] = icmp eq ptr %[[ARR_OFFS2]], null
!CHECK: %[[DATA_SEL:.*]] = select i1 %[[DATA_CMP]], i64 0, i64 %[[ALLOCATABLE_MEMBER_SIZE_CALC_5]]
!CHECK: %[[DATA_SEL:.*]] = select i1 %[[DATA_CMP]], i64 0, i64 %[[SIZE_ADJUSTED]]
!CHECK: %[[BASE_PTR_ARR:.*]] = getelementptr inbounds [5 x ptr], ptr %.offload_baseptrs, i32 0, i32 0
!CHECK: store ptr %[[ALLOCA]], ptr %[[BASE_PTR_ARR]], align 8
!CHECK: %[[OFFLOAD_PTR_ARR:.*]] = getelementptr inbounds [5 x ptr], ptr %.offload_ptrs, i32 0, i32 0
@ -762,10 +768,14 @@ end subroutine mapType_common_block_members
!CHECK: %[[OFF_PTR_CALC_2:.*]] = add i64 %[[OFF_PTR_CALC_1]], 1
!CHECK: %[[OFF_PTR_CALC_3:.*]] = mul i64 1, %[[OFF_PTR_CALC_2]]
!CHECK: %[[OFF_PTR_3:.*]] = mul i64 %[[OFF_PTR_CALC_3]], 104
!CHECK: %[[SIZE_ZERO_CMP_1:.*]] = icmp eq i64 %[[OFF_PTR_3]], 0
!CHECK: %[[SIZE_ADJUSTED_1:.*]] = select i1 %[[SIZE_ZERO_CMP_1]], i64 1, i64 %[[OFF_PTR_3]]
!CHECK: %[[SZ_CALC_1_2:.*]] = sub i64 %[[BOUNDS_CALC]], 0
!CHECK: %[[SZ_CALC_2_2:.*]] = add i64 %[[SZ_CALC_1_2]], 1
!CHECK: %[[SZ_CALC_3_2:.*]] = mul i64 1, %[[SZ_CALC_2_2]]
!CHECK: %[[SZ_CALC_4_2:.*]] = mul i64 %[[SZ_CALC_3_2]], 4
!CHECK: %[[SIZE_ZERO_CMP_2:.*]] = icmp eq i64 %[[SZ_CALC_4_2]], 0
!CHECK: %[[SIZE_ADJUSTED_2:.*]] = select i1 %[[SIZE_ZERO_CMP_2]], i64 1, i64 %[[SZ_CALC_4_2]]
!CHECK: %[[LOAD_OFF_PTR:.*]] = load ptr, ptr %[[OFF_PTR_2]], align 8
!CHECK: %[[ARR_OFFS:.*]] = getelementptr inbounds %_QFmaptype_nested_derived_type_member_idxTvertexes, ptr %[[LOAD_OFF_PTR]], i64 0
!CHECK: %[[LOAD_ARR_OFFS:.*]] = load ptr, ptr %[[OFF_PTR_4]], align 8
@ -779,9 +789,9 @@ end subroutine mapType_common_block_members
!CHECK: %[[SZ_CALC_3:.*]] = ptrtoaddr ptr %[[OFF_PTR_1]] to i64
!CHECK: %[[SZ_CALC_4:.*]] = sub i64 %[[SZ_CALC_2]], %[[SZ_CALC_3]]
!CHECK: %[[SIZE_CMP:.*]] = icmp eq ptr %[[ARR_OFFS_2]], null
!CHECK: %[[SIZE_SEL:.*]] = select i1 %[[SIZE_CMP]], i64 0, i64 %[[OFF_PTR_3]]
!CHECK: %[[SIZE_SEL:.*]] = select i1 %[[SIZE_CMP]], i64 0, i64 %[[SIZE_ADJUSTED_1]]
!CHECK: %[[SIZE_CMP2:.*]] = icmp eq ptr %[[ARR_OFFS_3]], null
!CHECK: %[[SIZE_SEL2:.*]] = select i1 %[[SIZE_CMP2]], i64 0, i64 %[[SZ_CALC_4_2]]
!CHECK: %[[SIZE_SEL2:.*]] = select i1 %[[SIZE_CMP2]], i64 0, i64 %[[SIZE_ADJUSTED_2]]
!CHECK: %[[SIZE_CMP3:.*]] = icmp eq ptr %[[ARR_OFFS]], null
!CHECK: %[[SIZE_SEL3:.*]] = select i1 %[[SIZE_CMP3]], i64 0, i64 64
!CHECK: %[[BASE_PTR_ARR:.*]] = getelementptr inbounds [8 x ptr], ptr %.offload_baseptrs, i32 0, i32 0

View file

@ -6,7 +6,7 @@ subroutine defaultmap_allocatable_present()
integer, dimension(:), allocatable :: arr
! CHECK: %[[MAP_1:.*]] = omp.map.info var_ptr({{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, !fir.box<!fir.heap<!fir.array<?xi32>>>) map_clauses(implicit, present) capture(ByRef) var_ptr_ptr({{.*}}) bounds({{.*}}) -> !fir.llvm_ptr<!fir.ref<!fir.array<?xi32>>> {name = ""}
! CHECK: %[[MAP_2:.*]] = omp.map.info var_ptr({{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, !fir.box<!fir.heap<!fir.array<?xi32>>>) map_clauses(always, implicit, to) capture(ByRef) members({{.*}}) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>> {name = "arr"}
! CHECK: %[[MAP_2:.*]] = omp.map.info var_ptr({{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, !fir.box<!fir.heap<!fir.array<?xi32>>>) map_clauses(always, implicit, present, to) capture(ByRef) members({{.*}}) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>> {name = "arr"}
!$omp target defaultmap(present: allocatable)
arr(1) = 10
!$omp end target

View file

@ -6221,6 +6221,27 @@ static uint64_t getArrayElementSizeInBits(LLVM::LLVMArrayType arrTy,
return dl.getTypeSizeInBits(arrTy.getElementType());
}
// The intent is to verify if the mapped data being passed is a
// pointer -> pointee that requires special handling in certain cases,
// e.g. applying the OMP_MAP_PTR_AND_OBJ map type.
//
// There may be a better way to verify this, but unfortunately with
// opaque pointers we lose the ability to easily check if something is
// a pointer whilst maintaining access to the underlying type.
static bool checkIfPointerMap(omp::MapInfoOp mapOp) {
// If we have a varPtrPtr field assigned then the underlying type is a pointer
if (mapOp.getVarPtrPtr())
return true;
// If the map data is declare target with a link clause, then it's represented
// as a pointer when we lower it to LLVM-IR even if at the MLIR level it has
// no relation to pointers.
if (isDeclareTargetLink(mapOp.getVarPtr()))
return true;
return false;
}
// This function calculates the size to be offloaded for a specified type, given
// its associated map clause (which can contain bounds information which affects
// the total size), this size is calculated based on the underlying element type
@ -6273,8 +6294,51 @@ static llvm::Value *getSizeInBytes(DataLayout &dl, const mlir::Type &type,
// size, so we do some on the fly runtime math to get the size in
// bytes from the extent (ub - lb) * sizeInBytes. NOTE: This may need
// some adjustment for members with more complex types.
return builder.CreateMul(elementCount,
builder.getInt64(underlyingTypeSzInBits / 8));
llvm::Value *sizeCalc = builder.CreateMul(
elementCount, builder.getInt64(underlyingTypeSzInBits / 8),
"element_count");
// This is a part of a "complicated" bit of size calculation logic that is
// in place to handle a couple of scenarios, one specific to Fortran and
// the other a more general OpenMP issue. The other piece of the
// calculation can be found as the final size calculation within the
// processIndividualMap function. Ideally we would move it here, but due
// to the complexity of calculating the final base address of some
// constructs (required for a nullary check), it's left as the final step.
// So, in the below 2 cases, the nullary check is in processIndividualMap
// and the size equality check is here. The cases this modifications help
// cover are:
//
// 1) If an argument has a null base pointer, then the size must be set to
// 0 to avoid the runtime exploding/complaining about an illegal
// pointer map. The size returning non-zero is feasible in certain
// cases if for example someone has specified there own bounds/range.
// 2) We wish to support a very specific OpenMP Fortran edge-case where a
// size zero array can be legally presence checked and found to be on
// device when it has been mapped. In these rare occasions the
// allocatable/pointer will have a size of 1 allocated for the
// underlying data, but this wall not be represented within the size of
// the descriptor, so we get a non-nullary pointer and a size of 0,
// allowing us to specify a size of 1 in these cases registering it on
// the device mapping table as present.
//
// The default fall through case is just returning the size calculation
// above, if we are not nullary and the size we calculate is non-zero,
// which is basically any pointer type that is allocated in someway
// (providing you are not running on a rare system that allows malloc's of
// size 0 with whatever caveats that may come with).
//
// Later in the nullary check in processIndividualMap it just devolves to
// selecting a size of 0 if we are nullary, if we are not, we will return
// either 1 or the calculated size, depending on the outcome of this
// select.
if (checkIfPointerMap(memberClause)) {
return builder.CreateSelect(
builder.CreateICmpEQ(sizeCalc, builder.getInt64(0)),
builder.getInt64(1), sizeCalc);
}
return sizeCalc;
}
}
@ -6749,27 +6813,6 @@ getOverlappedMembers(llvm::SmallVectorImpl<size_t> &overlapMapDataIdxs,
overlapMapDataIdxs.push_back(i);
}
// The intent is to verify if the mapped data being passed is a
// pointer -> pointee that requires special handling in certain cases,
// e.g. applying the OMP_MAP_PTR_AND_OBJ map type.
//
// There may be a better way to verify this, but unfortunately with
// opaque pointers we lose the ability to easily check if something is
// a pointer whilst maintaining access to the underlying type.
static bool checkIfPointerMap(omp::MapInfoOp mapOp) {
// If we have a varPtrPtr field assigned then the underlying type is a pointer
if (mapOp.getVarPtrPtr())
return true;
// If the map data is declare target with a link clause, then it's represented
// as a pointer when we lower it to LLVM-IR even if at the MLIR level it has
// no relation to pointers.
if (isDeclareTargetLink(mapOp.getVarPtr()))
return true;
return false;
}
/// This function handles the insertion of a single item of map data from
/// MapInfoData into the OMPIRBuilder's MapInfo list. Utilising this function
/// means the map being inserted can be treated as a non-parent map entity,

View file

@ -73,19 +73,23 @@ module attributes {omp.is_target_device = false, omp.target_triples = ["amdgcn-a
// CHECK: %[[FULL_ARR_SIZE2:.*]] = add i64 %[[FULL_ARR_SIZE3]], 1
// CHECK: %[[FULL_ARR_SIZE1:.*]] = mul i64 1, %[[FULL_ARR_SIZE2]]
// CHECK: %[[FULL_ARR_SIZE:.*]] = mul i64 %[[FULL_ARR_SIZE1]], 4
// CHECK: %[[FULL_ARR_SIZE_ZERO_CMP:.*]] = icmp eq i64 %[[FULL_ARR_SIZE]], 0
// CHECK: %[[FULL_ARR_SIZE_ADJUSTED:.*]] = select i1 %[[FULL_ARR_SIZE_ZERO_CMP]], i64 1, i64 %[[FULL_ARR_SIZE]]
// CHECK: %[[ARR_SECT_SIZE3:.*]] = sub i64 %[[ARR_SECT_SIZE4]], %[[ARR_SECT_OFFSET2]]
// CHECK: %[[ARR_SECT_SIZE2:.*]] = add i64 %[[ARR_SECT_SIZE3]], 1
// CHECK: %[[ARR_SECT_SIZE1:.*]] = mul i64 1, %[[ARR_SECT_SIZE2]]
// CHECK: %[[ARR_SECT_SIZE:.*]] = mul i64 %[[ARR_SECT_SIZE1]], 4
// CHECK: %[[ARR_SECT_SIZE_ZERO_CMP:.*]] = icmp eq i64 %[[ARR_SECT_SIZE]], 0
// CHECK: %[[ARR_SECT_SIZE_ADJUSTED:.*]] = select i1 %[[ARR_SECT_SIZE_ZERO_CMP]], i64 1, i64 %[[ARR_SECT_SIZE]]
// CHECK: %[[LFULL_ARR:.*]] = load ptr, ptr @full_arr, align 8
// CHECK: %[[FULL_ARR_PTR:.*]] = getelementptr inbounds float, ptr %[[LFULL_ARR]], i64 0
// CHECK: %[[LARR_SECT:.*]] = load ptr, ptr @sect_arr, align 8
// CHECK: %[[ARR_SECT_PTR:.*]] = getelementptr inbounds i32, ptr %[[LARR_SECT]], i64 %[[ARR_SECT_OFFSET2]]
// CHECK: %[[SCALAR_PTR_LOAD:.*]] = load ptr, ptr %[[SCALAR_BASE]], align 8
// CHECK: %[[NULL_CMP:.*]] = icmp eq ptr %[[FULL_ARR_PTR]], null
// CHECK: %[[IS_NULL:.*]] = select i1 %[[NULL_CMP]], i64 0, i64 %[[FULL_ARR_SIZE]]
// CHECK: %[[IS_NULL:.*]] = select i1 %[[NULL_CMP]], i64 0, i64 %[[FULL_ARR_SIZE_ADJUSTED]]
// CHECK: %[[NULL_CMP2:.*]] = icmp eq ptr %[[ARR_SECT_PTR]], null
// CHECK: %[[IS_NULL2:.*]] = select i1 %[[NULL_CMP2]], i64 0, i64 %[[ARR_SECT_SIZE]]
// CHECK: %[[IS_NULL2:.*]] = select i1 %[[NULL_CMP2]], i64 0, i64 %[[ARR_SECT_SIZE_ADJUSTED]]
// CHECK: %[[SCALAR_DESC_SZ4:.*]] = getelementptr { ptr, i64, i32, i8, i8, i8, i8 }, ptr %[[SCALAR_ALLOCA]], i32 1
// CHECK: %[[SCALAR_DESC_SZ3:.*]] = ptrtoaddr ptr %[[SCALAR_DESC_SZ4]] to i64
// CHECK: %[[SCALAR_DESC_SZ2:.*]] = ptrtoaddr ptr %[[SCALAR_ALLOCA]] to i64

View file

@ -0,0 +1,24 @@
! OpenMP offloading test that checks we correctly map zero sized arrays
! allowing subsequent presence checks to pass. This is legal in Fortran
! as zero sized arrays are a little bit different to the scenario where
! we have a not present argument or something that's not been allocated
! at all.
! REQUIRES: flang, amdgpu
! RUN: %libomptarget-compile-fortran-run-and-check-generic
program main
integer, allocatable :: array(:)
allocate(array(0))
!$omp target enter data map(to: array) 
! Despite there technically being no data, the
! presence check should legally return true here
! and not fail.
!$omp target map(present, alloc: array)
!$omp end target
print *, "PASS"
end program
! CHECK: PASS