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
|
function(collect_object_file_deps target result)
# NOTE: This function does add entrypoint targets to |result|.
# It is expected that the caller adds them separately.
set(all_deps "")
get_target_property(target_type ${target} "TARGET_TYPE")
if(NOT target_type)
return()
endif()
if(${target_type} STREQUAL ${OBJECT_LIBRARY_TARGET_TYPE})
list(APPEND all_deps ${target})
get_target_property(deps ${target} "DEPS")
foreach(dep IN LISTS deps)
collect_object_file_deps(${dep} dep_targets)
list(APPEND all_deps ${dep_targets})
endforeach(dep)
list(REMOVE_DUPLICATES all_deps)
set(${result} ${all_deps} PARENT_SCOPE)
return()
endif()
if(${target_type} STREQUAL ${ENTRYPOINT_OBJ_TARGET_TYPE} OR
${target_type} STREQUAL ${ENTRYPOINT_OBJ_VENDOR_TARGET_TYPE})
set(entrypoint_target ${target})
get_target_property(is_alias ${entrypoint_target} "IS_ALIAS")
if(is_alias)
get_target_property(aliasee ${entrypoint_target} "DEPS")
if(NOT aliasee)
message(FATAL_ERROR
"Entrypoint alias ${entrypoint_target} does not have an aliasee.")
endif()
set(entrypoint_target ${aliasee})
endif()
get_target_property(deps ${target} "DEPS")
foreach(dep IN LISTS deps)
collect_object_file_deps(${dep} dep_targets)
list(APPEND all_deps ${dep_targets})
endforeach(dep)
list(REMOVE_DUPLICATES all_deps)
set(${result} ${all_deps} PARENT_SCOPE)
return()
endif()
if(${target_type} STREQUAL ${ENTRYPOINT_EXT_TARGET_TYPE})
# It is not possible to recursively extract deps of external dependencies.
# So, we just accumulate the direct dep and return.
get_target_property(deps ${target} "DEPS")
set(${result} ${deps} PARENT_SCOPE)
return()
endif()
endfunction(collect_object_file_deps)
function(get_all_object_file_deps result fq_deps_list)
set(all_deps "")
foreach(dep ${fq_deps_list})
get_target_property(dep_type ${dep} "TARGET_TYPE")
if(NOT ((${dep_type} STREQUAL ${ENTRYPOINT_OBJ_TARGET_TYPE}) OR
(${dep_type} STREQUAL ${ENTRYPOINT_EXT_TARGET_TYPE}) OR
(${dep_type} STREQUAL ${ENTRYPOINT_OBJ_VENDOR_TARGET_TYPE})))
message(FATAL_ERROR "Dependency '${dep}' of 'add_entrypoint_collection' is "
"not an 'add_entrypoint_object' or 'add_entrypoint_external' target.")
endif()
collect_object_file_deps(${dep} recursive_deps)
list(APPEND all_deps ${recursive_deps})
# Add the entrypoint object target explicitly as collect_object_file_deps
# only collects object files from non-entrypoint targets.
if(${dep_type} STREQUAL ${ENTRYPOINT_OBJ_TARGET_TYPE} OR
${dep_type} STREQUAL ${ENTRYPOINT_OBJ_VENDOR_TARGET_TYPE})
set(entrypoint_target ${dep})
get_target_property(is_alias ${entrypoint_target} "IS_ALIAS")
if(is_alias)
get_target_property(aliasee ${entrypoint_target} "DEPS")
if(NOT aliasee)
message(FATAL_ERROR
"Entrypoint alias ${entrypoint_target} does not have an aliasee.")
endif()
set(entrypoint_target ${aliasee})
endif()
endif()
list(APPEND all_deps ${entrypoint_target})
endforeach(dep)
list(REMOVE_DUPLICATES all_deps)
set(${result} ${all_deps} PARENT_SCOPE)
endfunction()
# A rule to build a library from a collection of entrypoint objects and bundle
# it into a GPU fatbinary. Usage is the same as 'add_entrypoint_library'.
# Usage:
# add_gpu_entrypoint_library(
# DEPENDS <list of add_entrypoint_object targets>
# )
function(add_gpu_entrypoint_library target_name)
cmake_parse_arguments(
"ENTRYPOINT_LIBRARY"
"" # No optional arguments
"" # No single value arguments
"DEPENDS" # Multi-value arguments
${ARGN}
)
if(NOT ENTRYPOINT_LIBRARY_DEPENDS)
message(FATAL_ERROR "'add_entrypoint_library' target requires a DEPENDS list "
"of 'add_entrypoint_object' targets.")
endif()
get_fq_deps_list(fq_deps_list ${ENTRYPOINT_LIBRARY_DEPENDS})
get_all_object_file_deps(all_deps "${fq_deps_list}")
# The GPU 'libc' needs to be exported in a format that can be linked with
# offloading langauges like OpenMP or CUDA. This wraps every GPU object into a
# fat binary and adds them to a static library.
set(objects "")
foreach(dep IN LISTS all_deps)
set(object $<$<STREQUAL:$<TARGET_NAME_IF_EXISTS:${dep}>,${dep}>:$<TARGET_OBJECTS:${dep}>>)
string(FIND ${dep} "." last_dot_loc REVERSE)
math(EXPR name_loc "${last_dot_loc} + 1")
string(SUBSTRING ${dep} ${name_loc} -1 name)
if(LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
set(prefix --image=arch=generic,triple=nvptx64-nvidia-cuda,feature=+ptx63)
elseif(LIBC_TARGET_ARCHITECTURE_IS_AMDGPU)
set(prefix --image=arch=generic,triple=amdgcn-amd-amdhsa)
endif()
# Use the 'clang-offload-packager' to merge these files into a binary blob.
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/binary/${name}.gpubin"
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/binary
COMMAND ${LIBC_CLANG_OFFLOAD_PACKAGER}
"${prefix},file=$<JOIN:${object},,file=>" -o
${CMAKE_CURRENT_BINARY_DIR}/binary/${name}.gpubin
DEPENDS ${dep}
COMMENT "Packaging LLVM offloading binary for '${object}'"
)
add_custom_target(${dep}.__gpubin__ DEPENDS ${dep}
"${CMAKE_CURRENT_BINARY_DIR}/binary/${name}.gpubin")
# CMake does not permit setting the name on object files. In order to have
# human readable names we create an empty stub file with the entrypoint
# name. This empty file will then have the created binary blob embedded.
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/stubs/${name}.cpp"
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/stubs
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/stubs/${name}.cpp
DEPENDS ${dep} ${dep}.__gpubin__
)
add_custom_target(${dep}.__stub__
DEPENDS ${dep}.__gpubin__ "${CMAKE_CURRENT_BINARY_DIR}/stubs/${name}.cpp")
add_library(${dep}.__fatbin__
EXCLUDE_FROM_ALL OBJECT
"${CMAKE_CURRENT_BINARY_DIR}/stubs/${name}.cpp"
)
# This is always compiled for the LLVM host triple instead of the native GPU
# triple that is used by default in the build.
target_compile_options(${dep}.__fatbin__ BEFORE PRIVATE -nostdlib)
target_compile_options(${dep}.__fatbin__ PRIVATE
--target=${LLVM_HOST_TRIPLE}
"SHELL:-Xclang -fembed-offload-object=${CMAKE_CURRENT_BINARY_DIR}/binary/${name}.gpubin")
add_dependencies(${dep}.__fatbin__ ${dep} ${dep}.__stub__ ${dep}.__gpubin__)
# Set the list of newly create fat binaries containing embedded device code.
list(APPEND objects $<TARGET_OBJECTS:${dep}.__fatbin__>)
endforeach()
add_library(
${target_name}
STATIC
${objects}
)
set_target_properties(${target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${LIBC_LIBRARY_DIR})
endfunction(add_gpu_entrypoint_library)
# A rule to build a library from a collection of entrypoint objects.
# Usage:
# add_entrypoint_library(
# DEPENDS <list of add_entrypoint_object targets>
# )
#
# NOTE: If one wants an entrypoint to be available in a library, then they will
# have to list the entrypoint target explicitly in the DEPENDS list. Implicit
# entrypoint dependencies will not be added to the library.
function(add_entrypoint_library target_name)
cmake_parse_arguments(
"ENTRYPOINT_LIBRARY"
"" # No optional arguments
"" # No single value arguments
"DEPENDS" # Multi-value arguments
${ARGN}
)
if(NOT ENTRYPOINT_LIBRARY_DEPENDS)
message(FATAL_ERROR "'add_entrypoint_library' target requires a DEPENDS list "
"of 'add_entrypoint_object' targets.")
endif()
get_fq_deps_list(fq_deps_list ${ENTRYPOINT_LIBRARY_DEPENDS})
get_all_object_file_deps(all_deps "${fq_deps_list}")
set(objects "")
foreach(dep IN LISTS all_deps)
list(APPEND objects $<$<STREQUAL:$<TARGET_NAME_IF_EXISTS:${dep}>,${dep}>:$<TARGET_OBJECTS:${dep}>>)
endforeach(dep)
add_library(
${target_name}
STATIC
${objects}
)
set_target_properties(${target_name} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${LIBC_LIBRARY_DIR})
endfunction(add_entrypoint_library)
# Rule to build a shared library of redirector objects.
function(add_redirector_library target_name)
cmake_parse_arguments(
"REDIRECTOR_LIBRARY"
""
""
"DEPENDS"
${ARGN}
)
set(obj_files "")
foreach(dep IN LISTS REDIRECTOR_LIBRARY_DEPENDS)
# TODO: Ensure that each dep is actually a add_redirector_object target.
list(APPEND obj_files $<TARGET_OBJECTS:${dep}>)
endforeach(dep)
# TODO: Call the linker explicitly instead of calling the compiler driver to
# prevent DT_NEEDED on C++ runtime.
add_library(
${target_name}
EXCLUDE_FROM_ALL
SHARED
${obj_files}
)
set_target_properties(${target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${LIBC_LIBRARY_DIR})
target_link_libraries(${target_name} -nostdlib -lc -lm)
set_target_properties(${target_name} PROPERTIES LINKER_LANGUAGE "C")
endfunction(add_redirector_library)
set(HDR_LIBRARY_TARGET_TYPE "HDR_LIBRARY")
# Internal function, used by `add_header_library`.
function(create_header_library fq_target_name)
cmake_parse_arguments(
"ADD_HEADER"
"" # Optional arguments
"" # Single value arguments
"HDRS;DEPENDS;FLAGS;COMPILE_OPTIONS" # Multi-value arguments
${ARGN}
)
if(NOT ADD_HEADER_HDRS)
message(FATAL_ERROR "'add_header_library' target requires a HDRS list of .h files.")
endif()
if(SHOW_INTERMEDIATE_OBJECTS)
message(STATUS "Adding header library ${fq_target_name}")
if(${SHOW_INTERMEDIATE_OBJECTS} STREQUAL "DEPS")
foreach(dep IN LISTS ADD_HEADER_DEPENDS)
message(STATUS " ${fq_target_name} depends on ${dep}")
endforeach()
endif()
endif()
add_library(${fq_target_name} INTERFACE)
target_sources(${fq_target_name} INTERFACE ${ADD_HEADER_HDRS})
if(ADD_HEADER_DEPENDS)
add_dependencies(${fq_target_name} ${ADD_HEADER_DEPENDS})
# `*.__copied_hdr__` is created only to copy the header files to the target
# location, not to be linked against.
set(link_lib "")
foreach(dep ${ADD_HEADER_DEPENDS})
if (NOT dep MATCHES "__copied_hdr__")
list(APPEND link_lib ${dep})
endif()
endforeach()
target_link_libraries(${fq_target_name} INTERFACE ${link_lib})
endif()
if(ADD_HEADER_COMPILE_OPTIONS)
target_compile_options(${fq_target_name} INTERFACE ${ADD_HEADER_COMPILE_OPTIONS})
endif()
set_target_properties(
${fq_target_name}
PROPERTIES
INTERFACE_FLAGS "${ADD_HEADER_FLAGS}"
TARGET_TYPE "${HDR_LIBRARY_TARGET_TYPE}"
DEPS "${ADD_HEADER_DEPENDS}"
FLAGS "${ADD_HEADER_FLAGS}"
)
endfunction(create_header_library)
# Rule to add header only libraries.
# Usage
# add_header_library(
# <target name>
# HDRS <list of .h files part of the library>
# DEPENDS <list of dependencies>
# FLAGS <list of flags>
# )
function(add_header_library target_name)
add_target_with_flags(
${target_name}
CREATE_TARGET create_header_library
${ARGN}
)
endfunction(add_header_library)
|