llvm-project/flang/test/Transforms/omp-map-info-finalization-implicit-field.fir
Kareem Ergawy e82399dac2
[flang][OpenMP] Prevent omp.map.info ops with user-defined mappers from being marked as parial maps (#175133)
The following test was triggering a runtime crash **on the host before
launching the kernel**:
```fortran
program test_omp_target_map_bug_v5
  implicit none
  type nested_type
    real, allocatable :: alloc_field(:)
  end type nested_type

  type nesting_type
    integer :: int_field
    type(nested_type) :: derived_field
  end type nesting_type

  type(nesting_type) :: config

  allocate(config%derived_field%alloc_field(1))

  !$OMP TARGET ENTER DATA MAP(TO:config, config%derived_field%alloc_field)

  !$OMP TARGET
  config%derived_field%alloc_field(1) = 1.0
  !$OMP END TARGET

  deallocate(config%derived_field%alloc_field)
end program test_omp_target_map_bug_v5
```

In particular, the runtime was producing a segmentation fault when the
test is compiled with any optimization level > 0; if you compile with
-O0 the sample ran fine.

After debugging the runtime, it turned out the crash was happening at
the point where the runtime calls the default mapper emitted by the
compiler for `nesting_type; in particular at this point in the runtime:
c62cd2877c/offload/libomptarget/omptarget.cpp (L307).

Bisecting the optimization pipeline using `-mllvm -opt-bisect-limit=N`,
the first pass that triggered the issue on `O1` was the `instcombine`
pass. Debugging this further, the issue narrows down to canonicalizing
`getelementptr` instructions from using struct types (in this case the
`nesting_type` in the sample above) to using addressing bytes (`i8`). In
particular, in `O0`, you would see something like this:
```llvm
define internal void @.omp_mapper._QQFnesting_type_omp_default_mapper(ptr noundef %0, ptr noundef %1, ptr noundef %2, i64 noundef %3, i64 noundef %4, ptr noundef %5) #6 {
entry:
  %6 = udiv exact i64 %3, 56
  %7 = getelementptr %_QFTnesting_type, ptr %2, i64 %6
  ....
}
```

```llvm
define internal void @.omp_mapper._QQFnesting_type_omp_default_mapper(ptr noundef %0, ptr noundef %1, ptr noundef %2, i64 noundef %3, i64 noundef %4, ptr noundef %5) #6 {
entry:
  %6 = getelementptr i8, ptr %2, i64 %3
  ....
}
```

The `udiv exact` instruction emitted by the OMP IR Builder (see:
c62cd2877c/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp (L9154))
allows `instcombine` to assume that `%3` is divisible by the struct size
(here `56`) and, therefore, replaces the result of the division with
direct GEP on `i8` rather than the struct type.

However, the runtime was calling
`@.omp_mapper._QQFnesting_type_omp_default_mapper` not with `56` (the
proper struct size) but with `48`!

Debugging this further, I found that the size of `omp.map.info`
operation to which the default mapper is attached computes the value of
`48` because we set the map to partial (see:
c62cd2877c/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp (L1146)
and
c62cd2877c/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp (L4501-L4512)).

However, I think this is incorrect since the emitted mapper (and
user-defined mappers in general) are defined on the whole struct type
and should never be marked as partial. Hence, the fix in this PR.
2026-01-09 15:15:10 +01:00

88 lines
4.2 KiB
Plaintext

// Tests that we implicitly map alloctable fields of a record when referenced in
// a target region.
// RUN: fir-opt --split-input-file --omp-map-info-finalization %s | FileCheck %s
!record_t = !fir.type<_QFTrecord_t{
not_to_implicitly_map:
!fir.box<!fir.heap<!fir.array<?xf32>>>,
to_implicitly_map:
!fir.box<!fir.heap<!fir.array<?xf32>>>
}>
fir.global internal @_QFEdst_record : !record_t {
%0 = fir.undefined !record_t
fir.has_value %0 : !record_t
}
omp.declare_mapper @record_mapper : !record_t {
^bb0(%arg0: !fir.ref<!record_t>):
%0 = omp.map.info var_ptr(%arg0: !fir.ref<!record_t>, !record_t) map_clauses(implicit, tofrom) capture(ByRef) -> !fir.ref<!record_t>
omp.declare_mapper.info map_entries(%0: !fir.ref<!record_t>)
}
func.func @_QQmain() {
%6 = fir.address_of(@_QFEdst_record) : !fir.ref<!record_t>
%7:2 = hlfir.declare %6 {uniq_name = "_QFEdst_record"} : (!fir.ref<!record_t>) -> (!fir.ref<!record_t>, !fir.ref<!record_t>)
%16 = omp.map.info var_ptr(%7#1 : !fir.ref<!record_t>, !record_t) map_clauses(implicit, tofrom) capture(ByRef) -> !fir.ref<!record_t> {name = "dst_record"}
%17 = omp.map.info var_ptr(%7#1 : !fir.ref<!record_t>, !record_t) map_clauses(implicit, tofrom) capture(ByRef) mapper(@record_mapper) -> !fir.ref<!record_t> {name = "dst_record_with_mapper"}
omp.target map_entries(%16 -> %arg0, %17 -> %arg1 : !fir.ref<!record_t>, !fir.ref<!record_t>) {
%20:2 = hlfir.declare %arg0 {uniq_name = "_QFEdst_record"} : (!fir.ref<!record_t>) -> (!fir.ref<!record_t>, !fir.ref<!record_t>)
%21:2 = hlfir.declare %arg1 {uniq_name = "_QFEdst_record"} : (!fir.ref<!record_t>) -> (!fir.ref<!record_t>, !fir.ref<!record_t>)
%23 = hlfir.designate %20#0{"to_implicitly_map"} {fortran_attrs = #fir.var_attrs<allocatable>} : (!fir.ref<!record_t>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
%24 = hlfir.designate %21#0{"to_implicitly_map"} {fortran_attrs = #fir.var_attrs<allocatable>} : (!fir.ref<!record_t>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
omp.terminator
}
return
}
// CHECK: %[[RECORD_DECL:.*]]:2 = hlfir.declare %0 {uniq_name = "_QFEdst_record"}
// CHECK: %[[FIELD_COORD:.*]] = fir.coordinate_of %[[RECORD_DECL]]#1, to_implicitly_map
// CHECK: %[[UPPER_BOUND:.*]] = arith.subi %{{.*}}#1, %{{c1.*}} : index
// CHECK: %[[BOUNDS:.*]] = omp.map.bounds
// CHECK-SAME: lower_bound(%{{c0.*}} : index) upper_bound(%[[UPPER_BOUND]] : index)
// CHECK-SAME: extent(%{{.*}}#1 : index) stride(%{{.*}}#2 : index)
// CHECK-SAME: start_idx(%{{.*}}#0 : index) {stride_in_bytes = true}
// CHECK: %[[BASE_ADDR:.*]] = fir.box_offset %[[FIELD_COORD]] base_addr
// CHECK: %[[FIELD_BASE_ADDR_MAP:.*]] = omp.map.info var_ptr(
// CHECK-SAME: %[[FIELD_COORD]] : {{.*}}) map_clauses(
// CHECK-SAME: implicit, tofrom) capture(ByRef) var_ptr_ptr(
// CHECK-SAME: %[[BASE_ADDR]] : {{.*}}) bounds(
// CHECK-SAME: %[[BOUNDS]])
// CHECK: %[[FIELD_MAP:.*]] = omp.map.info var_ptr(
// CHECK-SAME: %[[FIELD_COORD]] : {{.*}}) map_clauses(
// CHECK-SAME: implicit, to) capture(ByRef) ->
// CHECK-SAME: {{.*}} {name = "dst_record.to_implicitly_map.implicit_map"}
// CHECK: %[[RECORD_MAP:.*]] = omp.map.info var_ptr(
// CHECK-SAME: %[[RECORD_DECL]]#1 : {{.*}}) map_clauses(
// CHECK-SAME: implicit, tofrom) capture(ByRef) members(
// CHECK-SAME: %[[FIELD_MAP]], %[[FIELD_BASE_ADDR_MAP]] :
// CHECK-SAME: [1], [1, 0] : {{.*}}) -> {{.*}}> {name =
// CHECK-SAME: "dst_record", partial_map = true}
// Verify map ops when using a mapper:
// Implicit field mapping is the same as for the non-mapper case.
// CHECK: omp.map.info
// CHECK: omp.map.info
// Verify that partial-map is not set if the map info op uses a user-defined (or
// compiler-emitted) mapper.
// CHECK: %[[RECORD_MAP_MAPPER:.*]] = omp.map.info var_ptr(
// CHECK-SAME: %[[RECORD_DECL]]#1 : {{.*}}) map_clauses(
// CHECK-SAME: implicit, tofrom) capture(ByRef) mapper(@record_mapper)
// CHECK-SAME: members(%{{.*}}, %{{.*}} : [1], [1, 0] : {{.*}}) -> {{.*}}> {name =
// CHECK-SAME: "dst_record_with_mapper"}
// CHECK: omp.target map_entries(
// CHECK-SAME: %[[RECORD_MAP]] -> %{{[^[:space:]]+}},
// CHECK-SAME: %[[FIELD_MAP]] -> %{{[^[:space:]]+}},
// CHECK-SAME: %[[FIELD_BASE_ADDR_MAP]] -> %{{[^[:space:]]+}}
// CHECK-SAME: : {{.*}})