summaryrefslogtreecommitdiff
path: root/openmp/tools/Modules/FindOpenMPTarget.cmake
blob: 424294090d5d0f983dbcc33bc7ee3fd40e84fb85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
##===----------------------------------------------------------------------===##
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
##===----------------------------------------------------------------------===##
#
# Find OpenMP Target offloading Support for various compilers.
#
##===----------------------------------------------------------------------===##

#[========================================================================[.rst:
FindOpenMPTarget
----------------

Finds OpenMP Target Offloading Support.

This module can be used to detect OpenMP target offloading support in a
compiler. If the compiler support OpenMP Offloading to a specified target, the
flags required to compile offloading code to that target are output for each
target.

This module will automatically include OpenMP support if it was not loaded
already. It does not need to be included separately to get full OpenMP support.

Variables
^^^^^^^^^

The module exposes the components ``NVPTX`` and ``AMDGPU``.  Each of these
controls the various offloading targets to search OpenMP target offloasing
support for.

Depending on the enabled components the following variables will be set:

``OpenMPTarget_FOUND``
  Variable indicating that OpenMP target offloading flags for all requested
  targets have been found.

This module will set the following variables per language in your
project, where ``<device>`` is one of NVPTX or AMDGPU

``OpenMPTarget_<device>_FOUND``
  Variable indicating if OpenMP support for the ``<device>`` was detected.
``OpenMPTarget_<device>_FLAGS``
  OpenMP compiler flags for offloading to ``<device>``, separated by spaces.

For linking with OpenMP code written in ``<device>``, the following
variables are provided:

``OpenMPTarget_<device>_LIBRARIES``
  A list of libraries needed to link with OpenMP code written in ``<lang>``.

Additionally, the module provides :prop_tgt:`IMPORTED` targets:

``OpenMPTarget::OpenMPTarget_<device>``
  Target for using OpenMP offloading to ``<device>``.

If the specific architecture of the target is needed, it can be manually
specified by setting a variable to the desired architecture. Variables can also
be used to override the standard flag searching for a given compiler.

``OpenMPTarget_<device>_ARCH``
  Sets the architecture of ``<device>`` to compile for. Such as `sm_70` for NVPTX
  or `gfx908` for AMDGPU. 

``OpenMPTarget_<device>_DEVICE``
  Sets the name of the device to offload to.

``OpenMPTarget_<device>_FLAGS``
  Sets the compiler flags for offloading to ``<device>``.

#]========================================================================]

# TODO: Support Fortran
# TODO: Support multiple offloading targets by setting the "OpenMPTarget" target
#       to include flags for all components loaded
# TODO: Configure target architecture without a variable (component NVPTX_SM_70)
# TODO: Test more compilers

cmake_policy(PUSH)
cmake_policy(VERSION 3.20.0)

find_package(OpenMP ${OpenMPTarget_FIND_VERSION} REQUIRED)

# Find the offloading flags for each compiler.
function(_OPENMP_TARGET_DEVICE_FLAG_CANDIDATES LANG DEVICE)
  if(NOT OpenMPTarget_${LANG}_FLAGS)
    unset(OpenMPTarget_FLAG_CANDIDATES)

    set(OMPTarget_FLAGS_Clang "-fopenmp-targets=${DEVICE}")
    set(OMPTarget_FLAGS_GNU "-foffload=${DEVICE}=\"-lm -latomic\"")
    set(OMPTarget_FLAGS_XL "-qoffload")
    set(OMPTarget_FLAGS_PGI "-mp=${DEVICE}")
    set(OMPTarget_FLAGS_NVHPC "-mp=${DEVICE}")

    if(DEFINED OMPTarget_FLAGS_${CMAKE_${LANG}_COMPILER_ID})
      set(OpenMPTarget_FLAG_CANDIDATES "${OMPTarget_FLAGS_${CMAKE_${LANG}_COMPILER_ID}}")
    endif()

    set(OpenMPTarget_${LANG}_FLAG_CANDIDATES "${OpenMPTarget_FLAG_CANDIDATES}" PARENT_SCOPE)
  else()
    set(OpenMPTarget_${LANG}_FLAG_CANDIDATES "${OpenMPTarget_${LANG}_FLAGS}" PARENT_SCOPE)
  endif()
endfunction()

# Get the coded name of the device for each compiler.
function(_OPENMP_TARGET_DEVICE_CANDIDATES LANG DEVICE)
  if (NOT OpenMPTarget_${DEVICE}_DEVICE)
    unset(OpenMPTarget_DEVICE_CANDIDATES)

    # Check each supported device.
    if("${DEVICE}" STREQUAL "NVPTX")
      if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
        set(OMPTarget_DEVICE_Clang "nvptx32-nvidia-cuda")
      else()
        set(OMPTarget_DEVICE_Clang "nvptx64-nvidia-cuda")
      endif()
      set(OMPTarget_DEVICE_GNU "nvptx-none")
      set(OMPTarget_DEVICE_XL "")
      set(OMPTarget_DEVICE_PGI "gpu")
      set(OMPTarget_DEVICE_NVHPC "gpu")

      if(DEFINED OMPTarget_DEVICE_${CMAKE_${LANG}_COMPILER_ID})
        set(OpenMPTarget_DEVICE_CANDIDATES "${OMPTarget_DEVICE_${CMAKE_${LANG}_COMPILER_ID}}")
      endif()
    elseif("${DEVICE}" STREQUAL "AMDGPU")
      set(OMPTarget_DEVICE_Clang "amdgcn-amd-amdhsa")
      set(OMPTarget_DEVICE_GNU "hsa")

      if(DEFINED OMPTarget_DEVICE_${CMAKE_${LANG}_COMPILER_ID})
        set(OpenMPTarget_DEVICE_CANDIDATES "${OMPTarget_DEVICE_${CMAKE_${LANG}_COMPILER_ID}}")
      endif()
    endif()
    set(OpenMPTarget_${LANG}_DEVICE_CANDIDATES "${OpenMPTarget_DEVICE_CANDIDATES}" PARENT_SCOPE)
  else()
    set(OpenMPTarget_${LANG}_DEVICE_CANDIDATES "${OpenMPTarget_${LANG}_DEVICE}" PARENT_SCOPE)
  endif()
endfunction()

# Get flags for setting the device's architecture for each compiler.
function(_OPENMP_TARGET_DEVICE_ARCH_CANDIDATES LANG DEVICE DEVICE_FLAG)
  if(OpenMPTarget_${DEVICE}_ARCH)
    # Only Clang supports selecting the architecture for now.
    set(OMPTarget_ARCH_Clang "-Xopenmp-target=${DEVICE_FLAG} -march=${OpenMPTarget_${DEVICE}_ARCH}")

    if(DEFINED OMPTarget_ARCH_${CMAKE_${LANG}_COMPILER_ID})
      set(OpenMPTarget_DEVICE_ARCH_CANDIDATES "${OMPTarget_ARCH_${CMAKE_${LANG}_COMPILER_ID}}")
    endif()
    set(OpenMPTarget_${LANG}_DEVICE_ARCH_CANDIDATES "${OpenMPTarget_DEVICE_ARCH_CANDIDATES}" PARENT_SCOPE)
  else()
    set(OpenMPTarget_${LANG}_DEVICE_ARCH_CANDIDATES "" PARENT_SCOPE)
  endif()
endfunction()

set(OpenMPTarget_C_CXX_TEST_SOURCE
"#include <omp.h>
int main(void) {
  int isHost;
#pragma omp target map(from: isHost)
  { isHost = omp_is_initial_device(); }
  return isHost;
}")

function(_OPENMP_TARGET_WRITE_SOURCE_FILE LANG SRC_FILE_CONTENT_VAR SRC_FILE_NAME SRC_FILE_FULLPATH)
  set(WORK_DIR ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FindOpenMPTarget)
  if("${LANG}" STREQUAL "C")
    set(SRC_FILE "${WORK_DIR}/${SRC_FILE_NAME}.c")
    file(WRITE "${SRC_FILE}" "${OpenMPTarget_C_CXX_${SRC_FILE_CONTENT_VAR}}")
  elseif("${LANG}" STREQUAL "CXX")
    set(SRC_FILE "${WORK_DIR}/${SRC_FILE_NAME}.cpp")
    file(WRITE "${SRC_FILE}" "${OpenMPTarget_C_CXX_${SRC_FILE_CONTENT_VAR}}")
  endif()
  set(${SRC_FILE_FULLPATH} "${SRC_FILE}" PARENT_SCOPE)
endfunction()

# Get the candidate flags and try to compile a test application. If it compiles
# and all the flags are found, we assume the compiler supports offloading.
function(_OPENMP_TARGET_DEVICE_GET_FLAGS LANG DEVICE OPENMP_FLAG_VAR OPENMP_LIB_VAR OPENMP_DEVICE_VAR OPENMP_ARCH_VAR)
  _OPENMP_TARGET_DEVICE_CANDIDATES(${LANG} ${DEVICE})
  _OPENMP_TARGET_DEVICE_FLAG_CANDIDATES(${LANG} "${OpenMPTarget_${LANG}_DEVICE_CANDIDATES}")
  _OPENMP_TARGET_DEVICE_ARCH_CANDIDATES(${LANG} ${DEVICE} "${OpenMPTarget_${LANG}_DEVICE_CANDIDATES}")
  _OPENMP_TARGET_WRITE_SOURCE_FILE("${LANG}" "TEST_SOURCE" OpenMPTargetTryFlag _OPENMP_TEST_SRC)

  # Try to compile a test application with the found flags.
  try_compile(OpenMPTarget_COMPILE_RESULT ${CMAKE_BINARY_DIR} ${_OPENMP_TEST_SRC}
    CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OpenMP_${LANG}_FLAGS} ${OpenMPTarget_${LANG}_FLAG_CANDIDATES} ${OpenMPTarget_${LANG}_DEVICE_ARCH_CANDIDATES}"
    "-DINCLUDE_DIRECTORIES:STRING=${OpenMP_${LANG}_INCLUDE_DIR}"
    LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG}
    OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT
  )

  file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
    "Detecting OpenMP ${CMAKE_${LANG}_COMPILER_ID} ${DEVICE} target support with the following Flags:
    ${OpenMP_${LANG}_FLAGS} ${OpenMPTarget_${LANG}_FLAG_CANDIDATES} ${OpenMPTarget_${LANG}_DEVICE_ARCH_CANDIDATES}
    With the following output:\n ${OpenMP_TRY_COMPILE_OUTPUT}\n")

  # If compilation was successful and the device was found set the return variables.
  if (OpenMPTarget_COMPILE_RESULT AND DEFINED OpenMPTarget_${LANG}_DEVICE_CANDIDATES)
    file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
      "Compilation successful, adding flags for ${DEVICE}.\n\n")

    # Clang has a seperate library for target offloading.
    if(CMAKE_${LANG}_COMPILER_ID STREQUAL "Clang")
      find_library(OpenMPTarget_libomptarget_LIBRARY
        NAMES omptarget
        HINTS ${CMAKE_${LANG}_IMPLICIT_LINK_DIRECTORIES}
      )
      mark_as_advanced(OpenMPTarget_libomptarget_LIBRARY)
      set("${OPENMP_LIB_VAR}" "${OpenMPTarget_libomptarget_LIBRARY}" PARENT_SCOPE)
    else()
      unset("${OPENMP_LIB_VAR}" PARENT_SCOPE)
    endif()
    set("${OPENMP_DEVICE_VAR}" "${OpenMPTarget_${LANG}_DEVICE_CANDIDATES}" PARENT_SCOPE)
    set("${OPENMP_FLAG_VAR}" "${OpenMPTarget_${LANG}_FLAG_CANDIDATES}" PARENT_SCOPE)
    set("${OPENMP_ARCH_VAR}" "${OpenMPTarget_${LANG}_DEVICE_ARCH_CANDIDATES}" PARENT_SCOPE)
  else()
    unset("${OPENMP_DEVICE_VAR}" PARENT_SCOPE)
    unset("${OPENMP_FLAG_VAR}" PARENT_SCOPE)
    unset("${OPENMP_ARCH_VAR}" PARENT_SCOPE)
  endif()
endfunction()

# Load the compiler support for each device.
foreach(LANG IN ITEMS C CXX)
  # Cache the version in case CMake doesn't load the OpenMP package this time
  set(OpenMP_${LANG}_VERSION ${OpenMP_${LANG}_VERSION}
    CACHE STRING "OpenMP Version" FORCE)
  mark_as_advanced(OpenMP_${LANG}_VERSION)
  foreach(DEVICE IN ITEMS NVPTX AMDGPU)
    if(CMAKE_${LANG}_COMPILER_LOADED)
      if(NOT DEFINED OpenMPTarget_${LANG}_FLAGS OR NOT DEFINED OpenMPTarget_${LANG}_DEVICE)
        _OPENMP_TARGET_DEVICE_GET_FLAGS(${LANG} ${DEVICE}
          OpenMPTarget_${DEVICE}_FLAGS_WORK
          OpenMPTarget_${DEVICE}_LIBS_WORK
          OpenMPTarget_${DEVICE}_DEVICE_WORK
          OpenMPTarget_${DEVICE}_ARCHS_WORK)

        separate_arguments(_OpenMPTarget_${DEVICE}_FLAGS NATIVE_COMMAND "${OpenMPTarget_${DEVICE}_FLAGS_WORK}")
        separate_arguments(_OpenMPTarget_${DEVICE}_ARCHS NATIVE_COMMAND "${OpenMPTarget_${DEVICE}_ARCHS_WORK}")
        set(OpenMPTarget_${DEVICE}_FLAGS ${_OpenMPTarget_${DEVICE}_FLAGS}
            CACHE STRING "${DEVICE} target compile flags for OpenMP target offloading" FORCE)
        set(OpenMPTarget_${DEVICE}_ARCH ${_OpenMPTarget_${DEVICE}_ARCHS}
            CACHE STRING "${DEVICE} target architecture flags for OpenMP target offloading" FORCE)
        set(OpenMPTarget_${DEVICE}_LIBRARIES ${OpenMPTarget_${DEVICE}_LIBS_WORK}
            CACHE STRING "${DEVICE} target libraries for OpenMP target offloading" FORCE)
        mark_as_advanced(OpenMPTarget_${DEVICE}_FLAGS OpenMPTarget_${DEVICE}_ARCH OpenMPTarget_${DEVICE}_LIBRARIES)
      endif()
    endif()
  endforeach()
endforeach()

if(OpenMPTarget_FIND_COMPONENTS)
  set(OpenMPTarget_FINDLIST ${OpenMPTarget_FIND_COMPONENTS})
else()
  set(OpenMPTarget_FINDLIST NVPTX)
endif()

unset(_OpenMPTarget_MIN_VERSION)

# Attempt to find each requested device.
foreach(LANG IN ITEMS C CXX)
  foreach(DEVICE IN LISTS OpenMPTarget_FINDLIST)
    if(CMAKE_${LANG}_COMPILER_LOADED)
      set(OpenMPTarget_${DEVICE}_VERSION "${OpenMP_${LANG}_VERSION}")
      set(OpenMPTarget_${DEVICE}_VERSION_MAJOR "${OpenMP_${LANG}_VERSION}_MAJOR")
      set(OpenMPTarget_${DEVICE}_VERSION_MINOR "${OpenMP_${LANG}_VERSION}_MINOR")
      set(OpenMPTarget_${DEVICE}_FIND_QUIETLY ${OpenMPTarget_FIND_QUIETLY})
      set(OpenMPTarget_${DEVICE}_FIND_REQUIRED ${OpenMPTarget_FIND_REQUIRED})
      set(OpenMPTarget_${DEVICE}_FIND_VERSION ${OpenMPTarget_FIND_VERSION})
      set(OpenMPTarget_${DEVICE}_FIND_VERSION_EXACT ${OpenMPTarget_FIND_VERSION_EXACT})

      # OpenMP target offloading is only supported in OpenMP 4.0 an newer.
      if(OpenMPTarget_${DEVICE}_VERSION AND ("${OpenMPTarget_${DEVICE}_VERSION}" VERSION_LESS "4.0"))
        message(SEND_ERROR "FindOpenMPTarget requires at least OpenMP 4.0")
      endif()

      set(FPHSA_NAME_MISMATCHED TRUE)
      find_package_handle_standard_args(OpenMPTarget_${DEVICE}
        REQUIRED_VARS OpenMPTarget_${DEVICE}_FLAGS
        VERSION_VAR OpenMPTarget_${DEVICE}_VERSION)

      if(OpenMPTarget_${DEVICE}_FOUND)
        if(DEFINED OpenMPTarget_${DEVICE}_VERSION)
          if(NOT _OpenMPTarget_MIN_VERSION OR _OpenMPTarget_MIN_VERSION VERSION_GREATER OpenMPTarget_${LANG}_VERSION)
            set(_OpenMPTarget_MIN_VERSION OpenMPTarget_${DEVICE}_VERSION)
          endif()
        endif()
        # Create a new target.
        if(NOT TARGET OpenMPTarget::OpenMPTarget_${DEVICE})
          add_library(OpenMPTarget::OpenMPTarget_${DEVICE} INTERFACE IMPORTED)
        endif()
        # Get compiler flags for offloading to the device and architecture and
        # set the target features. Include the normal OpenMP flags as well.
        set_property(TARGET OpenMPTarget::OpenMPTarget_${DEVICE} PROPERTY
          INTERFACE_COMPILE_OPTIONS 
          "$<$<COMPILE_LANGUAGE:${LANG}>:${OpenMPTarget_${DEVICE}_FLAGS}>"
          "$<$<COMPILE_LANGUAGE:${LANG}>:${OpenMPTarget_${DEVICE}_ARCH}>"
          "$<$<COMPILE_LANGUAGE:${LANG}>:${OpenMP_${LANG}_FLAGS}>")
        set_property(TARGET OpenMPTarget::OpenMPTarget_${DEVICE} PROPERTY
          INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${OpenMP_${LANG}_INCLUDE_DIRS}>")
        set_property(TARGET OpenMPTarget::OpenMPTarget_${DEVICE} PROPERTY
          INTERFACE_LINK_LIBRARIES 
          "${OpenMPTarget_${DEVICE}_LIBRARIES}"
          "${OpenMP_${LANG}_LIBRARIES}")
        # The offloading flags must also be passed during the linking phase so
        # the compiler can pass the binary to the correct toolchain.
        set_property(TARGET OpenMPTarget::OpenMPTarget_${DEVICE} PROPERTY
          INTERFACE_LINK_OPTIONS 
          "$<$<COMPILE_LANGUAGE:${LANG}>:${OpenMPTarget_${DEVICE}_FLAGS}>"
          "$<$<COMPILE_LANGUAGE:${LANG}>:${OpenMPTarget_${DEVICE}_ARCH}>"
          "$<$<COMPILE_LANGUAGE:${LANG}>:${OpenMP_${LANG}_FLAGS}>")
        # Combine all the flags if not using the target for convenience.
        set(OpenMPTarget_${DEVICE}_FLAGS ${OpenMP_${LANG}_FLAGS}
          ${OpenMPTarget_${DEVICE}_FLAGS} 
          ${OpenMPTarget_${DEVICE}_ARCH}
          CACHE STRING "${DEVICE} target compile flags for OpenMP target offloading" FORCE)
      endif()
    endif()
  endforeach()
endforeach()

unset(_OpenMPTarget_REQ_VARS)
foreach(DEVICE IN LISTS OpenMPTarget_FINDLIST)
  list(APPEND _OpenMPTarget_REQ_VARS "OpenMPTarget_${DEVICE}_FOUND")
endforeach()

find_package_handle_standard_args(OpenMPTarget
    REQUIRED_VARS ${_OpenMPTarget_REQ_VARS}
    VERSION_VAR ${_OpenMPTarget_MIN_VERSION}
    HANDLE_COMPONENTS)

if(NOT (CMAKE_C_COMPILER_LOADED OR CMAKE_CXX_COMPILER_LOADED) OR CMAKE_Fortran_COMPILER_LOADED)
  message(SEND_ERROR "FindOpenMPTarget requires the C or CXX languages to be enabled")
endif()

unset(OpenMPTarget_C_CXX_TEST_SOURCE)
cmake_policy(POP)