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.
88 lines
4.2 KiB
Plaintext
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: : {{.*}})
|