Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}