xref: /llvm-project/libclc/cmake/modules/AddLibclc.cmake (revision 76befc86dea9cad6be870c04732379f7ecf596dd)
1# Compiles an OpenCL C - or assembles an LL file - to bytecode
2#
3# Arguments:
4# * TRIPLE <string>
5#     Target triple for which to compile the bytecode file.
6# * INPUT <string>
7#     File to compile/assemble to bytecode
8# * OUTPUT <string>
9#     Bytecode file to generate
10# * EXTRA_OPTS <string> ...
11#     List of compiler options to use. Note that some are added by default.
12# * DEPENDENCIES <string> ...
13#     List of extra dependencies to inject
14#
15# Depends on the clang, llvm-as, and llvm-link targets for compiling,
16# assembling, and linking, respectively.
17function(compile_to_bc)
18  cmake_parse_arguments(ARG
19    ""
20    "TRIPLE;INPUT;OUTPUT"
21    "EXTRA_OPTS;DEPENDENCIES"
22    ${ARGN}
23  )
24
25  # If this is an LLVM IR file (identified soley by its file suffix),
26  # pre-process it with clang to a temp file, then assemble that to bytecode.
27  set( TMP_SUFFIX )
28  get_filename_component( FILE_EXT ${ARG_INPUT} EXT )
29  if( NOT ${FILE_EXT} STREQUAL ".ll" )
30    # Pass '-c' when not running the preprocessor
31    set( PP_OPTS -c )
32  else()
33    set( PP_OPTS -E;-P )
34    set( TMP_SUFFIX .tmp )
35  endif()
36
37  set( TARGET_ARG )
38  if( ARG_TRIPLE )
39    set( TARGET_ARG "-target" ${ARG_TRIPLE} )
40  endif()
41
42  # Ensure the directory we are told to output to exists
43  get_filename_component( ARG_OUTPUT_DIR ${ARG_OUTPUT} DIRECTORY )
44  file( MAKE_DIRECTORY ${ARG_OUTPUT_DIR} )
45
46  add_custom_command(
47    OUTPUT ${ARG_OUTPUT}${TMP_SUFFIX}
48    COMMAND ${clang_exe}
49      ${TARGET_ARG}
50      ${PP_OPTS}
51      ${ARG_EXTRA_OPTS}
52      -MD -MF ${ARG_OUTPUT}.d -MT ${ARG_OUTPUT}${TMP_SUFFIX}
53      # LLVM 13 enables standard includes by default - we don't want
54      # those when pre-processing IR. We disable it unconditionally.
55      $<$<VERSION_GREATER_EQUAL:${LLVM_PACKAGE_VERSION},13.0.0>:-cl-no-stdinc>
56      -emit-llvm
57      -o ${ARG_OUTPUT}${TMP_SUFFIX}
58      -x cl
59      ${ARG_INPUT}
60    DEPENDS
61      ${clang_target}
62      ${ARG_INPUT}
63      ${ARG_DEPENDENCIES}
64    DEPFILE ${ARG_OUTPUT}.d
65  )
66
67  if( ${FILE_EXT} STREQUAL ".ll" )
68    add_custom_command(
69      OUTPUT ${ARG_OUTPUT}
70      COMMAND ${llvm-as_exe} -o ${ARG_OUTPUT} ${ARG_OUTPUT}${TMP_SUFFIX}
71      DEPENDS ${llvm-as_target} ${ARG_OUTPUT}${TMP_SUFFIX}
72    )
73  endif()
74endfunction()
75
76# Links together one or more bytecode files
77#
78# Arguments:
79# * INTERNALIZE
80#     Set if -internalize flag should be passed when linking
81# * TARGET <string>
82#     Custom target to create
83# * INPUT <string> ...
84#     List of bytecode files to link together
85# * DEPENDENCIES <string> ...
86#     List of extra dependencies to inject
87function(link_bc)
88  cmake_parse_arguments(ARG
89    "INTERNALIZE"
90    "TARGET"
91    "INPUTS;DEPENDENCIES"
92    ${ARGN}
93  )
94
95  set( LINK_INPUT_ARG ${ARG_INPUTS} )
96  if( WIN32 OR CYGWIN )
97    # Create a response file in case the number of inputs exceeds command-line
98    # character limits on certain platforms.
99    file( TO_CMAKE_PATH ${LIBCLC_ARCH_OBJFILE_DIR}/${ARG_TARGET}.rsp RSP_FILE )
100    # Turn it into a space-separate list of input files
101    list( JOIN ARG_INPUTS " " RSP_INPUT )
102    file( GENERATE OUTPUT ${RSP_FILE} CONTENT ${RSP_INPUT} )
103    # Ensure that if this file is removed, we re-run CMake
104    set_property( DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
105      ${RSP_FILE}
106    )
107    set( LINK_INPUT_ARG "@${RSP_FILE}" )
108  endif()
109
110  add_custom_command(
111    OUTPUT ${ARG_TARGET}.bc
112    COMMAND ${llvm-link_exe} $<$<BOOL:${ARG_INTERNALIZE}>:--internalize> -o ${ARG_TARGET}.bc ${LINK_INPUT_ARG}
113    DEPENDS ${llvm-link_target} ${ARG_DEPENDENCIES} ${ARG_INPUTS} ${RSP_FILE}
114  )
115
116  add_custom_target( ${ARG_TARGET} ALL DEPENDS ${ARG_TARGET}.bc )
117  set_target_properties( ${ARG_TARGET} PROPERTIES
118    TARGET_FILE ${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}.bc
119    FOLDER "libclc/Device IR/Linking"
120  )
121endfunction()
122
123# Decomposes and returns variables based on a libclc triple and architecture
124# combination. Returns data via one or more optional output variables.
125#
126# Arguments:
127# * TRIPLE <string>
128#     libclc target triple to query
129# * DEVICE <string>
130#     libclc device to query
131#
132# Optional Arguments:
133# * CPU <var>
134#     Variable name to be set to the target CPU
135# * ARCH_SUFFIX <var>
136#     Variable name to be set to the triple/architecture suffix
137# * CLANG_TRIPLE <var>
138#     Variable name to be set to the normalized clang triple
139function(get_libclc_device_info)
140  cmake_parse_arguments(ARG
141    ""
142    "TRIPLE;DEVICE;CPU;ARCH_SUFFIX;CLANG_TRIPLE"
143    ""
144    ${ARGN}
145  )
146
147  if( NOT ARG_TRIPLE OR NOT ARG_DEVICE )
148    message( FATAL_ERROR "Must provide both TRIPLE and DEVICE" )
149  endif()
150
151  string( REPLACE "-" ";" TRIPLE  ${ARG_TRIPLE} )
152  list( GET TRIPLE 0 ARCH )
153
154  # Some targets don't have a specific device architecture to target
155  if( ARG_DEVICE STREQUAL none OR ARCH STREQUAL spirv OR ARCH STREQUAL spirv64 )
156    set( cpu )
157    set( arch_suffix "${ARG_TRIPLE}" )
158  else()
159    set( cpu "${ARG_DEVICE}" )
160    set( arch_suffix "${ARG_DEVICE}-${ARG_TRIPLE}" )
161  endif()
162
163  if( ARG_CPU )
164    set( ${ARG_CPU} ${cpu} PARENT_SCOPE )
165  endif()
166
167  if( ARG_ARCH_SUFFIX )
168    set( ${ARG_ARCH_SUFFIX} ${arch_suffix} PARENT_SCOPE )
169  endif()
170
171  # Some libclc targets are not real clang triples: return their canonical
172  # triples.
173  if( ARCH STREQUAL spirv OR ARCH STREQUAL clspv )
174    set( ARG_TRIPLE "spir--" )
175  elseif( ARCH STREQUAL spirv64 OR ARCH STREQUAL clspv64 )
176    set( ARG_TRIPLE "spir64--" )
177  endif()
178
179  if( ARG_CLANG_TRIPLE )
180    set( ${ARG_CLANG_TRIPLE} ${ARG_TRIPLE} PARENT_SCOPE )
181  endif()
182endfunction()
183
184# Compiles a list of library source files (provided by LIB_FILES/GEN_FILES) and
185# compiles them to LLVM bytecode (or SPIR-V), links them together and optimizes
186# them.
187#
188# For bytecode libraries, a list of ALIASES may optionally be provided to
189# produce additional symlinks.
190#
191# Arguments:
192#  * ARCH <string>
193#      libclc architecture being built
194#  * ARCH_SUFFIX <string>
195#      libclc architecture/triple suffix
196#  * TRIPLE <string>
197#      Triple used to compile
198#
199# Optional Arguments:
200# * CLC_INTERNAL
201#     Pass if compiling the internal CLC builtin libraries, which are not
202#     optimized and do not have aliases created.
203#  * LIB_FILES <string> ...
204#      List of files that should be built for this library
205#  * GEN_FILES <string> ...
206#      List of generated files (in build dir) that should be built for this library
207#  * COMPILE_FLAGS <string> ...
208#      Compilation options (for clang)
209#  * OPT_FLAGS <string> ...
210#      Optimization options (for opt)
211#  * ALIASES <string> ...
212#      List of aliases
213#  * INTERNAL_LINK_DEPENDENCIES <string> ...
214#      A list of extra bytecode files to link into the builtin library. Symbols
215#      from these link dependencies will be internalized during linking.
216function(add_libclc_builtin_set)
217  cmake_parse_arguments(ARG
218    "CLC_INTERNAL"
219    "ARCH;TRIPLE;ARCH_SUFFIX"
220    "LIB_FILES;GEN_FILES;COMPILE_FLAGS;OPT_FLAGS;ALIASES;INTERNAL_LINK_DEPENDENCIES"
221    ${ARGN}
222  )
223
224  if( NOT ARG_ARCH OR NOT ARG_ARCH_SUFFIX OR NOT ARG_TRIPLE )
225    message( FATAL_ERROR "Must provide ARCH, ARCH_SUFFIX, and TRIPLE" )
226  endif()
227
228  set( bytecode_files "" )
229  foreach( file IN LISTS ARG_GEN_FILES ARG_LIB_FILES )
230    # We need to take each file and produce an absolute input file, as well
231    # as a unique architecture-specific output file. We deal with a mix of
232    # different input files, which makes this trickier.
233    if( ${file} IN_LIST ARG_GEN_FILES )
234      # Generated files are given just as file names, which we must make
235      # absolute to the binary directory.
236      set( input_file ${CMAKE_CURRENT_BINARY_DIR}/${file} )
237      set( output_file "${LIBCLC_ARCH_OBJFILE_DIR}/${file}.bc" )
238    else()
239      # Other files are originally relative to each SOURCE file, which are
240      # then make relative to the libclc root directory. We must normalize
241      # the path (e.g., ironing out any ".."), then make it relative to the
242      # root directory again, and use that relative path component for the
243      # binary path.
244      get_filename_component( abs_path ${file} ABSOLUTE BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR} )
245      file( RELATIVE_PATH root_rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${abs_path} )
246      set( input_file ${CMAKE_CURRENT_SOURCE_DIR}/${file} )
247      set( output_file "${LIBCLC_ARCH_OBJFILE_DIR}/${root_rel_path}.bc" )
248    endif()
249
250    get_filename_component( file_dir ${file} DIRECTORY )
251
252    compile_to_bc(
253      TRIPLE ${ARG_TRIPLE}
254      INPUT ${input_file}
255      OUTPUT ${output_file}
256      EXTRA_OPTS -fno-builtin -nostdlib
257        "${ARG_COMPILE_FLAGS}" -I${CMAKE_CURRENT_SOURCE_DIR}/${file_dir}
258      DEPENDENCIES generate_convert.cl clspv-generate_convert.cl
259    )
260    list( APPEND bytecode_files ${output_file} )
261  endforeach()
262
263  set( builtins_comp_lib_tgt builtins.comp.${ARG_ARCH_SUFFIX} )
264  add_custom_target( ${builtins_comp_lib_tgt}
265    DEPENDS ${bytecode_files}
266  )
267  set_target_properties( ${builtins_comp_lib_tgt} PROPERTIES FOLDER "libclc/Device IR/Comp" )
268
269  if( NOT bytecode_files )
270    message(FATAL_ERROR "Cannot create an empty builtins library")
271  endif()
272
273  set( builtins_link_lib_tgt builtins.link.${ARG_ARCH_SUFFIX} )
274
275  if( NOT ARG_INTERNAL_LINK_DEPENDENCIES )
276    link_bc(
277      TARGET ${builtins_link_lib_tgt}
278      INPUTS ${bytecode_files}
279      DEPENDENCIES ${builtins_comp_lib_tgt}
280    )
281  else()
282    # If we have libraries to link while internalizing their symbols, we need
283    # two separate link steps; the --internalize flag applies to all link
284    # inputs but the first.
285    set( builtins_link_lib_tmp_tgt builtins.link.pre-deps.${ARG_ARCH_SUFFIX} )
286    link_bc(
287      TARGET ${builtins_link_lib_tmp_tgt}
288      INPUTS ${bytecode_files}
289      DEPENDENCIES ${builtins_comp_lib_tgt}
290    )
291    link_bc(
292      INTERNALIZE
293      TARGET ${builtins_link_lib_tgt}
294      INPUTS $<TARGET_PROPERTY:${builtins_link_lib_tmp_tgt},TARGET_FILE>
295        ${ARG_INTERNAL_LINK_DEPENDENCIES}
296      DEPENDENCIES ${builtins_link_lib_tmp_tgt}
297    )
298  endif()
299
300  # For the CLC internal builtins, exit here - we only optimize the targets'
301  # entry points once we've linked the CLC buitins into them
302  if( ARG_CLC_INTERNAL )
303    return()
304  endif()
305
306  set( builtins_link_lib $<TARGET_PROPERTY:${builtins_link_lib_tgt},TARGET_FILE> )
307
308  if( ARG_ARCH STREQUAL spirv OR ARG_ARCH STREQUAL spirv64 )
309    set( spv_suffix ${ARG_ARCH_SUFFIX}.spv )
310    add_custom_command( OUTPUT ${spv_suffix}
311      COMMAND ${llvm-spirv_exe} ${spvflags} -o ${spv_suffix} ${builtins_link_lib}
312      DEPENDS ${llvm-spirv_target} ${builtins_link_lib} ${builtins_link_lib_tgt}
313    )
314    add_custom_target( "prepare-${spv_suffix}" ALL DEPENDS "${spv_suffix}" )
315    set_target_properties( "prepare-${spv_suffix}" PROPERTIES FOLDER "libclc/Device IR/Prepare" )
316    install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${spv_suffix}
317       DESTINATION "${CMAKE_INSTALL_DATADIR}/clc" )
318
319    return()
320  endif()
321
322  set( builtins_opt_lib_tgt builtins.opt.${ARG_ARCH_SUFFIX} )
323
324  # Add opt target
325  add_custom_command( OUTPUT ${builtins_opt_lib_tgt}.bc
326    COMMAND ${opt_exe} ${ARG_OPT_FLAGS} -o ${builtins_opt_lib_tgt}.bc
327      ${builtins_link_lib}
328    DEPENDS ${opt_target} ${builtins_link_lib} ${builtins_link_lib_tgt}
329  )
330  add_custom_target( ${builtins_opt_lib_tgt}
331    ALL DEPENDS ${builtins_opt_lib_tgt}.bc
332  )
333  set_target_properties( ${builtins_opt_lib_tgt} PROPERTIES
334    TARGET_FILE ${CMAKE_CURRENT_BINARY_DIR}/${builtins_opt_lib_tgt}.bc
335    FOLDER "libclc/Device IR/Opt"
336  )
337
338  set( builtins_opt_lib $<TARGET_PROPERTY:${builtins_opt_lib_tgt},TARGET_FILE> )
339
340  # Add prepare target
341  set( obj_suffix ${ARG_ARCH_SUFFIX}.bc )
342  add_custom_command( OUTPUT ${obj_suffix}
343    COMMAND ${prepare_builtins_exe} -o ${obj_suffix} ${builtins_opt_lib}
344    DEPENDS ${builtins_opt_lib} ${builtins_opt_lib_tgt} ${prepare_builtins_target} )
345  add_custom_target( prepare-${obj_suffix} ALL DEPENDS ${obj_suffix} )
346  set_target_properties( "prepare-${obj_suffix}" PROPERTIES FOLDER "libclc/Device IR/Prepare" )
347
348  # nvptx-- targets don't include workitem builtins
349  if( NOT ARG_TRIPLE MATCHES ".*ptx.*--$" )
350    add_test( NAME external-calls-${obj_suffix}
351      COMMAND ./check_external_calls.sh ${CMAKE_CURRENT_BINARY_DIR}/${obj_suffix} ${LLVM_TOOLS_BINARY_DIR}
352      WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} )
353  endif()
354
355  install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${obj_suffix} DESTINATION "${CMAKE_INSTALL_DATADIR}/clc" )
356  foreach( a ${ARG_ALIASES} )
357    set( alias_suffix "${a}-${ARG_TRIPLE}.bc" )
358    add_custom_command(
359      OUTPUT ${alias_suffix}
360      COMMAND ${CMAKE_COMMAND} -E create_symlink ${obj_suffix} ${alias_suffix}
361      DEPENDS prepare-${obj_suffix} )
362    add_custom_target( alias-${alias_suffix} ALL DEPENDS ${alias_suffix} )
363    set_target_properties( alias-${alias_suffix} PROPERTIES FOLDER "libclc/Device IR/Aliases" )
364    install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${alias_suffix}
365             DESTINATION "${CMAKE_INSTALL_DATADIR}/clc" )
366  endforeach( a )
367endfunction(add_libclc_builtin_set)
368
369# Produces a list of libclc source files by walking over SOURCES files in a
370# given directory. Outputs the list of files in LIB_FILE_LIST.
371#
372# LIB_FILE_LIST may be pre-populated and is appended to.
373#
374# Arguments:
375# * CLC_INTERNAL
376#     Pass if compiling the internal CLC builtin libraries, which have a
377#     different directory structure.
378# * LIB_ROOT_DIR <string>
379#     Root directory containing target's lib files, relative to libclc root
380#     directory. If not provided, is set to '.'.
381# * DIRS <string> ...
382#     List of directories under LIB_ROOT_DIR to walk over searching for SOURCES
383#     files
384function(libclc_configure_lib_source LIB_FILE_LIST)
385  cmake_parse_arguments(ARG
386    "CLC_INTERNAL"
387    "LIB_ROOT_DIR"
388    "DIRS"
389    ${ARGN}
390  )
391
392  if( NOT ARG_LIB_ROOT_DIR )
393    set(ARG_LIB_ROOT_DIR  ".")
394  endif()
395
396  # Enumerate SOURCES* files
397  set( source_list )
398  foreach( l ${ARG_DIRS} )
399    foreach( s "SOURCES" "SOURCES_${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}" )
400      if( ARG_CLC_INTERNAL )
401        file( TO_CMAKE_PATH ${ARG_LIB_ROOT_DIR}/lib/${l}/${s} file_loc )
402      else()
403        file( TO_CMAKE_PATH ${ARG_LIB_ROOT_DIR}/${l}/lib/${s} file_loc )
404      endif()
405      file( TO_CMAKE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${file_loc} loc )
406      # Prepend the location to give higher priority to
407      # specialized implementation
408      if( EXISTS ${loc} )
409        set( source_list ${file_loc} ${source_list} )
410      endif()
411    endforeach()
412  endforeach()
413
414  ## Add the generated convert files here to prevent adding the ones listed in
415  ## SOURCES
416  set( rel_files ${${LIB_FILE_LIST}} ) # Source directory input files, relative to the root dir
417  set( objects ${${LIB_FILE_LIST}} )   # A "set" of already-added input files
418
419  foreach( l ${source_list} )
420    file( READ ${l} file_list )
421    string( REPLACE "\n" ";" file_list ${file_list} )
422    get_filename_component( dir ${l} DIRECTORY )
423    foreach( f ${file_list} )
424      # Only add each file once, so that targets can 'specialize' builtins
425      if( NOT ${f} IN_LIST objects )
426        list( APPEND objects ${f} )
427        list( APPEND rel_files ${dir}/${f} )
428      endif()
429    endforeach()
430  endforeach()
431
432  set( ${LIB_FILE_LIST} ${rel_files} PARENT_SCOPE )
433endfunction(libclc_configure_lib_source LIB_FILE_LIST)
434