/src/CMake/Source/cmLinkLineDeviceComputer.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | |
4 | | #include "cmLinkLineDeviceComputer.h" |
5 | | |
6 | | #include <algorithm> |
7 | | #include <initializer_list> |
8 | | #include <set> |
9 | | #include <utility> |
10 | | #include <vector> |
11 | | |
12 | | #include <cmext/algorithm> |
13 | | |
14 | | #include "cmComputeLinkInformation.h" |
15 | | #include "cmGeneratorTarget.h" |
16 | | #include "cmGlobalGenerator.h" |
17 | | #include "cmLinkItem.h" |
18 | | #include "cmList.h" |
19 | | #include "cmListFileCache.h" |
20 | | #include "cmLocalGenerator.h" |
21 | | #include "cmMakefile.h" |
22 | | #include "cmStateDirectory.h" |
23 | | #include "cmStateSnapshot.h" |
24 | | #include "cmStringAlgorithms.h" |
25 | | #include "cmTargetTypes.h" |
26 | | #include "cmValue.h" |
27 | | |
28 | | class cmOutputConverter; |
29 | | |
30 | | cmLinkLineDeviceComputer::cmLinkLineDeviceComputer( |
31 | | cmOutputConverter* outputConverter, cmStateDirectory const& stateDir, |
32 | | std::string language) |
33 | 0 | : cmLinkLineComputer(outputConverter, stateDir) |
34 | 0 | , Language(std::move(language)) |
35 | 0 | { |
36 | 0 | } |
37 | | |
38 | 0 | cmLinkLineDeviceComputer::~cmLinkLineDeviceComputer() = default; |
39 | | |
40 | | static bool cmLinkItemValidForDevice(std::string const& item) |
41 | 0 | { |
42 | | // Valid items are: |
43 | | // * Non-flags (does not start in '-') |
44 | | // * Specific flags --library, --library-path, -l, -L |
45 | | // For example: |
46 | | // * 'cublas_device' => pass-along |
47 | | // * '--library pthread' => pass-along |
48 | | // * '-lpthread' => pass-along |
49 | | // * '-pthread' => drop |
50 | | // * '-a' => drop |
51 | | // * '-framework Name' (as one string) => drop |
52 | 0 | return (!cmHasPrefix(item, '-') || // |
53 | 0 | cmHasLiteralPrefix(item, "-l") || // |
54 | 0 | cmHasLiteralPrefix(item, "-L") || // |
55 | 0 | cmHasLiteralPrefix(item, "--library")); |
56 | 0 | } |
57 | | |
58 | | bool cmLinkLineDeviceComputer::ComputeRequiresDeviceLinking( |
59 | | cmComputeLinkInformation& cli) |
60 | 0 | { |
61 | | // Determine if this item might requires device linking. |
62 | | // For this we only consider targets |
63 | 0 | using ItemVector = cmComputeLinkInformation::ItemVector; |
64 | 0 | ItemVector const& items = cli.GetItems(); |
65 | 0 | return std::any_of( |
66 | 0 | items.begin(), items.end(), |
67 | 0 | [this](cmComputeLinkInformation::Item const& item) -> bool { |
68 | 0 | return item.Target && |
69 | 0 | item.Target->GetType() == cm::TargetType::STATIC_LIBRARY && |
70 | | // this dependency requires us to device link it |
71 | 0 | !item.Target->GetPropertyAsBool( |
72 | 0 | cmStrCat(this->Language, "_RESOLVE_DEVICE_SYMBOLS")) && |
73 | 0 | item.Target->GetPropertyAsBool( |
74 | 0 | cmStrCat(this->Language, "_SEPARABLE_COMPILATION")); |
75 | 0 | }); |
76 | 0 | } |
77 | | |
78 | | bool cmLinkLineDeviceComputer::ComputeRequiresDeviceLinkingIPOFlag( |
79 | | cmComputeLinkInformation& cli) |
80 | 0 | { |
81 | | // Determine if this item might requires device linking. |
82 | | // For this we only consider targets |
83 | 0 | using ItemVector = cmComputeLinkInformation::ItemVector; |
84 | 0 | ItemVector const& items = cli.GetItems(); |
85 | 0 | std::string config = cli.GetConfig(); |
86 | 0 | return std::any_of( |
87 | 0 | items.begin(), items.end(), |
88 | 0 | [this, config](cmComputeLinkInformation::Item const& item) -> bool { |
89 | 0 | return item.Target && |
90 | 0 | item.Target->GetType() == cm::TargetType::STATIC_LIBRARY && |
91 | | // this dependency requires us to device link it |
92 | 0 | !item.Target->GetPropertyAsBool( |
93 | 0 | cmStrCat(this->Language, "_RESOLVE_DEVICE_SYMBOLS")) && |
94 | 0 | item.Target->GetPropertyAsBool( |
95 | 0 | cmStrCat(this->Language, "_SEPARABLE_COMPILATION")) && |
96 | 0 | item.Target->IsIPOEnabled(this->Language, config); |
97 | 0 | }); |
98 | 0 | } |
99 | | |
100 | | void cmLinkLineDeviceComputer::ComputeLinkLibraries( |
101 | | cmComputeLinkInformation& cli, std::string const& stdLibString, |
102 | | std::vector<BT<std::string>>& linkLibraries) |
103 | 0 | { |
104 | | // Generate the unique set of link items when device linking. |
105 | | // The nvcc device linker is designed so that each static library |
106 | | // with device symbols only needs to be listed once as it doesn't |
107 | | // care about link order. |
108 | 0 | std::set<std::string> emitted; |
109 | 0 | using ItemVector = cmComputeLinkInformation::ItemVector; |
110 | 0 | ItemVector const& items = cli.GetItems(); |
111 | 0 | std::string config = cli.GetConfig(); |
112 | 0 | bool skipItemAfterFramework = false; |
113 | 0 | auto const librariesBegin = linkLibraries.size(); |
114 | 0 | cmMakefile const* mf = cli.GetTarget()->Makefile; |
115 | 0 | std::string const wholeArchive = |
116 | 0 | cmJoin(cmList(mf->GetSafeDefinition(cmStrCat( |
117 | 0 | "CMAKE_", this->Language, "_DEVICE_LINK_OPTIONS_WHOLE_ARCHIVE"))), |
118 | 0 | " "); |
119 | 0 | std::string const noWholeArchive = cmJoin( |
120 | 0 | cmList(mf->GetSafeDefinition(cmStrCat( |
121 | 0 | "CMAKE_", this->Language, "_DEVICE_LINK_OPTIONS_NO_WHOLE_ARCHIVE"))), |
122 | 0 | " "); |
123 | |
|
124 | 0 | for (auto const& item : items) { |
125 | 0 | if (skipItemAfterFramework) { |
126 | 0 | skipItemAfterFramework = false; |
127 | 0 | continue; |
128 | 0 | } |
129 | | |
130 | 0 | if (item.Target) { |
131 | 0 | bool skip = false; |
132 | 0 | switch (item.Target->GetType()) { |
133 | 0 | case cm::TargetType::SHARED_LIBRARY: |
134 | 0 | case cm::TargetType::MODULE_LIBRARY: |
135 | 0 | case cm::TargetType::OBJECT_LIBRARY: |
136 | 0 | case cm::TargetType::INTERFACE_LIBRARY: |
137 | 0 | skip = true; |
138 | 0 | break; |
139 | 0 | case cm::TargetType::STATIC_LIBRARY: |
140 | 0 | skip = item.Target->GetPropertyAsBool( |
141 | 0 | cmStrCat(this->Language, "_RESOLVE_DEVICE_SYMBOLS")); |
142 | 0 | break; |
143 | 0 | default: |
144 | 0 | break; |
145 | 0 | } |
146 | 0 | if (skip) { |
147 | 0 | continue; |
148 | 0 | } |
149 | 0 | } |
150 | | |
151 | 0 | BT<std::string> linkLib; |
152 | 0 | if (item.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) { |
153 | | // nvcc understands absolute paths to libraries ending in '.o', .a', or |
154 | | // '.lib'. These should be passed to nvlink. Other extensions need to be |
155 | | // left out because nvlink may not understand or need them. Even though |
156 | | // it can tolerate '.so' or '.dylib' it cannot tolerate '.so.1'. |
157 | 0 | if (cmHasLiteralSuffix(item.Value.Value, ".o") || |
158 | 0 | cmHasLiteralSuffix(item.Value.Value, ".obj") || |
159 | 0 | cmHasLiteralSuffix(item.Value.Value, ".a") || |
160 | 0 | cmHasLiteralSuffix(item.Value.Value, ".lib")) { |
161 | 0 | linkLib.Value = item |
162 | 0 | .GetFormattedItem(this->ConvertToOutputFormat( |
163 | 0 | this->ConvertToLinkReference(item.Value.Value))) |
164 | 0 | .Value; |
165 | 0 | } |
166 | 0 | } else if (item.Value == "-framework") { |
167 | | // This is the first part of '-framework Name' where the framework |
168 | | // name is specified as a following item. Ignore both. |
169 | 0 | skipItemAfterFramework = true; |
170 | 0 | continue; |
171 | 0 | } else if (cmLinkItemValidForDevice(item.Value.Value)) { |
172 | 0 | linkLib.Value = item.Value.Value; |
173 | 0 | } |
174 | | |
175 | 0 | if (!linkLib.Value.empty() && emitted.insert(linkLib.Value).second) { |
176 | 0 | linkLib.Value += " "; |
177 | |
|
178 | 0 | cmLinkImplementation const* linkImpl = |
179 | 0 | cli.GetTarget()->GetLinkImplementation(cli.GetConfig(), |
180 | 0 | cmGeneratorTarget::UseTo::Link); |
181 | |
|
182 | 0 | for (cmLinkItem const& iter : linkImpl->Libraries) { |
183 | 0 | if (iter.Target && |
184 | 0 | iter.Target->GetType() != cm::TargetType::INTERFACE_LIBRARY) { |
185 | 0 | std::string libPath = iter.Target->GetLocation(cli.GetConfig()); |
186 | 0 | if (item.Value == libPath) { |
187 | 0 | linkLib.Backtrace = iter.Backtrace; |
188 | 0 | break; |
189 | 0 | } |
190 | 0 | } |
191 | 0 | } |
192 | |
|
193 | 0 | linkLibraries.emplace_back(linkLib); |
194 | 0 | } |
195 | 0 | } |
196 | | |
197 | 0 | if (!stdLibString.empty()) { |
198 | 0 | linkLibraries.emplace_back(cmStrCat(stdLibString, ' ')); |
199 | 0 | } |
200 | |
|
201 | 0 | if (linkLibraries.size() > librariesBegin && !wholeArchive.empty()) { |
202 | 0 | linkLibraries.insert(linkLibraries.begin() + librariesBegin, |
203 | 0 | BT<std::string>(cmStrCat(wholeArchive, ' '))); |
204 | 0 | linkLibraries.emplace_back(cmStrCat(noWholeArchive, ' ')); |
205 | 0 | } |
206 | 0 | } |
207 | | |
208 | | std::string cmLinkLineDeviceComputer::GetLinkerLanguage(cmGeneratorTarget*, |
209 | | std::string const&) |
210 | 0 | { |
211 | 0 | return this->Language; |
212 | 0 | } |
213 | | |
214 | | bool requireDeviceLinking(cmGeneratorTarget const& target, |
215 | | cmLocalGenerator& lg, std::string const& config, |
216 | | std::string const& language) |
217 | 0 | { |
218 | 0 | if (!target.GetGlobalGenerator()->GetLanguageEnabled(language)) { |
219 | 0 | return false; |
220 | 0 | } |
221 | | |
222 | 0 | if (target.GetType() == cm::TargetType::OBJECT_LIBRARY) { |
223 | 0 | return false; |
224 | 0 | } |
225 | | |
226 | 0 | if (!lg.GetMakefile()->IsOn( |
227 | 0 | cmStrCat("CMAKE_", language, "_COMPILER_HAS_DEVICE_LINK_PHASE"))) { |
228 | 0 | return false; |
229 | 0 | } |
230 | | |
231 | 0 | if (cmValue resolveDeviceSymbols = |
232 | 0 | target.GetProperty(cmStrCat(language, "_RESOLVE_DEVICE_SYMBOLS"))) { |
233 | | // If CUDA_RESOLVE_DEVICE_SYMBOLS has been explicitly set we need |
234 | | // to honor the value no matter what it is. |
235 | 0 | return resolveDeviceSymbols.IsOn(); |
236 | 0 | } |
237 | | |
238 | | // Determine if we have any dependencies that require |
239 | | // us to do a device link step |
240 | 0 | cmGeneratorTarget::LinkClosure const* closure = |
241 | 0 | target.GetLinkClosure(config); |
242 | |
|
243 | 0 | if (cm::contains(closure->Languages, language)) { |
244 | 0 | if (target.GetProperty(cmStrCat(language, "_SEPARABLE_COMPILATION")) |
245 | 0 | .IsOn()) { |
246 | 0 | bool doDeviceLinking = false; |
247 | 0 | switch (target.GetType()) { |
248 | 0 | case cm::TargetType::SHARED_LIBRARY: |
249 | 0 | case cm::TargetType::MODULE_LIBRARY: |
250 | 0 | case cm::TargetType::EXECUTABLE: |
251 | 0 | doDeviceLinking = true; |
252 | 0 | break; |
253 | 0 | default: |
254 | 0 | break; |
255 | 0 | } |
256 | 0 | return doDeviceLinking; |
257 | 0 | } |
258 | | |
259 | 0 | cmComputeLinkInformation* pcli = target.GetLinkInformation(config); |
260 | 0 | if (pcli) { |
261 | 0 | cmLinkLineDeviceComputer deviceLinkComputer( |
262 | 0 | &lg, lg.GetStateSnapshot().GetDirectory(), language); |
263 | 0 | return deviceLinkComputer.ComputeRequiresDeviceLinking(*pcli); |
264 | 0 | } |
265 | 0 | return true; |
266 | 0 | } |
267 | 0 | return false; |
268 | 0 | } |
269 | | |
270 | | std::string deviceLinkLanguage(cmGeneratorTarget& target, cmLocalGenerator& lg, |
271 | | std::string const& config) |
272 | 0 | { |
273 | 0 | for (auto const* language : { "CUDA", "SYCL" }) { |
274 | 0 | if (requireDeviceLinking(target, lg, config, language)) { |
275 | 0 | return language; |
276 | 0 | } |
277 | 0 | } |
278 | 0 | return {}; |
279 | 0 | } |