[flang][OpenMP] The schedule clause is ignored for target offload
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
The `schedule` clause has no effect on a target construct in flang. Every schedule kind and chunk
size produces identical code, and the iteration-to-thread mapping is always that of
`schedule(static,1)`.
`schedule(static,C)` prescribes which thread runs which iteration, so the mapping is directly
observable. Recording `omp_get_thread_num()` per iteration with `num_teams(1) thread_limit(8)` and
32 iterations, on gfx90a:
| clause | clang, the C equivalent | flang |
|---|---|---|
| `schedule(static,1)` | `0 1 2 3 4 5 6 7 0 1 ...` | `0 1 2 3 4 5 6 7 0 1 ...` |
| `schedule(static,4)` | `0 0 0 0 1 1 1 1 ...` | `0 1 2 3 4 5 6 7 ...` |
| `schedule(static,8)` | `0 0 0 0 0 0 0 0 ...` | `0 1 2 3 4 5 6 7 ...` |
| none | `0 1 2 3 4 5 6 7 ...` | `0 1 2 3 4 5 6 7 ...` |
Deterministic: one unique output over 20 runs of each.
```fortran
program p
use omp_lib
implicit none
integer, parameter :: n = 32
integer :: tid(n), i
tid = -1
!$omp target teams distribute parallel do num_teams(1) thread_limit(8) schedule(static,4) map(tofrom:tid)
do i = 1, n
tid(i) = omp_get_thread_num()
end do
write(*,'(A,32I3)') " tid: ", tid
end program
```
At the IR level every variant emits a byte-identical call. `schedule(static,8)`,
`schedule(dynamic)`, `schedule(dynamic,4)`, `schedule(guided)`, `schedule(runtime)`,
`schedule(monotonic:static,4)`, `schedule(nonmonotonic:dynamic)`, `schedule(simd:static,4)`,
`dist_schedule(static,4)` and no clause at all all produce:
```
call void @__kmpc_distribute_for_static_loop_4u(..., i32 128, i32 %2, i32 0, i32 0, i8 0)
num_iters nthr blockchunk threadchunk
```
clang emits `__kmpc_dispatch_init_4` / `__kmpc_dispatch_next_4` for `dynamic` and `guided`.
## Cause
`OpenMPIRBuilder::applyWorkshareLoop` receives the schedule information and the device path drops it
(`llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp`):
```cpp
if (Config.isTargetDevice())
return applyWorkshareLoopTarget(DL, CLI, AllocaIP, LoopType, NoLoop);
```
`SchedKind`, `ChunkSize`, `HasDistSchedule`, `DistScheduleChunkSize` and the simd/monotonic
modifiers are all discarded, and `applyWorkshareLoopTarget` always passes `block_chunk = 0` and
`thread_chunk = 0`.
`HasOrderedClause` was dropped by the same line, which is #214257; #214263 fixes that one by falling
through to the generic path when `ordered` is present. The same approach may extend here, but a
non-default `schedule` on device is a performance decision rather than a correctness one, so it
seemed worth filing separately rather than widening that patch.
A side effect: `StaticLoopChunker::NormalizedLoopNestChunked` in the DeviceRTL is unreachable from
flang, since the chunk arguments are always zero.
## Scope
The chunk being ignored is deterministic and observable, as above. `schedule(dynamic)` also
collapsing to static is clear in the IR, but I could not demonstrate a runtime difference: on a
deliberately load-imbalanced loop clang's `dynamic` was no faster than its `static` on this GPU, so
I am not claiming a performance impact for that part.
Contributor guide
Research direction
Start in llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp, reading applyWorkshareLoop and applyWorkshareLoopTarget, then compare the generated IR for the Fortran reproducer's schedule clauses. Done means device compilation preserves the relevant schedule and chunk information instead of always emitting zero chunks; verify this with the shown runtime mapping and IR call.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, fortran
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100