/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/Driver/ToolChains/HIPAMD.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===--- HIPAMD.cpp - HIP Tool and ToolChain Implementations ----*- C++ -*-===// |
2 | | // |
3 | | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | | // See https://llvm.org/LICENSE.txt for license information. |
5 | | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | | // |
7 | | //===----------------------------------------------------------------------===// |
8 | | |
9 | | #include "HIPAMD.h" |
10 | | #include "AMDGPU.h" |
11 | | #include "CommonArgs.h" |
12 | | #include "HIPUtility.h" |
13 | | #include "clang/Basic/Cuda.h" |
14 | | #include "clang/Basic/TargetID.h" |
15 | | #include "clang/Driver/Compilation.h" |
16 | | #include "clang/Driver/Driver.h" |
17 | | #include "clang/Driver/DriverDiagnostic.h" |
18 | | #include "clang/Driver/InputInfo.h" |
19 | | #include "clang/Driver/Options.h" |
20 | | #include "clang/Driver/SanitizerArgs.h" |
21 | | #include "llvm/Support/Alignment.h" |
22 | | #include "llvm/Support/FileSystem.h" |
23 | | #include "llvm/Support/Path.h" |
24 | | #include "llvm/TargetParser/TargetParser.h" |
25 | | |
26 | | using namespace clang::driver; |
27 | | using namespace clang::driver::toolchains; |
28 | | using namespace clang::driver::tools; |
29 | | using namespace clang; |
30 | | using namespace llvm::opt; |
31 | | |
32 | | #if defined(_WIN32) || defined(_WIN64) |
33 | | #define NULL_FILE "nul" |
34 | | #else |
35 | | #define NULL_FILE "/dev/null" |
36 | | #endif |
37 | | |
38 | | static bool shouldSkipSanitizeOption(const ToolChain &TC, |
39 | | const llvm::opt::ArgList &DriverArgs, |
40 | | StringRef TargetID, |
41 | 8.94k | const llvm::opt::Arg *A) { |
42 | | // For actions without targetID, do nothing. |
43 | 8.94k | if (TargetID.empty()) |
44 | 4.62k | return false; |
45 | 4.32k | Option O = A->getOption(); |
46 | 4.32k | if (!O.matches(options::OPT_fsanitize_EQ)) |
47 | 4.29k | return false; |
48 | | |
49 | 29 | if (!DriverArgs.hasFlag(options::OPT_fgpu_sanitize, |
50 | 29 | options::OPT_fno_gpu_sanitize, true)) |
51 | 13 | return true; |
52 | | |
53 | 16 | auto &Diags = TC.getDriver().getDiags(); |
54 | | |
55 | | // For simplicity, we only allow -fsanitize=address |
56 | 16 | SanitizerMask K = parseSanitizerValue(A->getValue(), /*AllowGroups=*/false); |
57 | 16 | if (K != SanitizerKind::Address) |
58 | 6 | return true; |
59 | | |
60 | 10 | llvm::StringMap<bool> FeatureMap; |
61 | 10 | auto OptionalGpuArch = parseTargetID(TC.getTriple(), TargetID, &FeatureMap); |
62 | | |
63 | 10 | assert(OptionalGpuArch && "Invalid Target ID"); |
64 | 10 | (void)OptionalGpuArch; |
65 | 10 | auto Loc = FeatureMap.find("xnack"); |
66 | 10 | if (Loc == FeatureMap.end() || !Loc->second8 ) { |
67 | 4 | Diags.Report( |
68 | 4 | clang::diag::warn_drv_unsupported_option_for_offload_arch_req_feature) |
69 | 4 | << A->getAsString(DriverArgs) << TargetID << "xnack+"; |
70 | 4 | return true; |
71 | 4 | } |
72 | 6 | return false; |
73 | 10 | } |
74 | | |
75 | | void AMDGCN::Linker::constructLlvmLinkCommand(Compilation &C, |
76 | | const JobAction &JA, |
77 | | const InputInfoList &Inputs, |
78 | | const InputInfo &Output, |
79 | 2 | const llvm::opt::ArgList &Args) const { |
80 | | // Construct llvm-link command. |
81 | | // The output from llvm-link is a bitcode file. |
82 | 2 | ArgStringList LlvmLinkArgs; |
83 | | |
84 | 2 | assert(!Inputs.empty() && "Must have at least one input."); |
85 | | |
86 | 2 | LlvmLinkArgs.append({"-o", Output.getFilename()}); |
87 | 2 | for (auto Input : Inputs) |
88 | 3 | LlvmLinkArgs.push_back(Input.getFilename()); |
89 | | |
90 | | // Look for archive of bundled bitcode in arguments, and add temporary files |
91 | | // for the extracted archive of bitcode to inputs. |
92 | 2 | auto TargetID = Args.getLastArgValue(options::OPT_mcpu_EQ); |
93 | 2 | AddStaticDeviceLibsLinking(C, *this, JA, Inputs, Args, LlvmLinkArgs, "amdgcn", |
94 | 2 | TargetID, |
95 | 2 | /*IsBitCodeSDL=*/true, |
96 | 2 | /*PostClangLink=*/false); |
97 | | |
98 | 2 | const char *LlvmLink = |
99 | 2 | Args.MakeArgString(getToolChain().GetProgramPath("llvm-link")); |
100 | 2 | C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(), |
101 | 2 | LlvmLink, LlvmLinkArgs, Inputs, |
102 | 2 | Output)); |
103 | 2 | } |
104 | | |
105 | | void AMDGCN::Linker::constructLldCommand(Compilation &C, const JobAction &JA, |
106 | | const InputInfoList &Inputs, |
107 | | const InputInfo &Output, |
108 | 295 | const llvm::opt::ArgList &Args) const { |
109 | | // Construct lld command. |
110 | | // The output from ld.lld is an HSA code object file. |
111 | 295 | ArgStringList LldArgs{"-flavor", |
112 | 295 | "gnu", |
113 | 295 | "-m", |
114 | 295 | "elf64_amdgpu", |
115 | 295 | "--no-undefined", |
116 | 295 | "-shared", |
117 | 295 | "-plugin-opt=-amdgpu-internalize-symbols"}; |
118 | | |
119 | 295 | auto &TC = getToolChain(); |
120 | 295 | auto &D = TC.getDriver(); |
121 | 295 | assert(!Inputs.empty() && "Must have at least one input."); |
122 | 295 | bool IsThinLTO = D.getLTOMode(/*IsOffload=*/true) == LTOK_Thin; |
123 | 295 | addLTOOptions(TC, Args, LldArgs, Output, Inputs[0], IsThinLTO); |
124 | | |
125 | | // Extract all the -m options |
126 | 295 | std::vector<llvm::StringRef> Features; |
127 | 295 | amdgpu::getAMDGPUTargetFeatures(D, TC.getTriple(), Args, Features); |
128 | | |
129 | | // Add features to mattr such as cumode |
130 | 295 | std::string MAttrString = "-plugin-opt=-mattr="; |
131 | 295 | for (auto OneFeature : unifyTargetFeatures(Features)) { |
132 | 48 | MAttrString.append(Args.MakeArgString(OneFeature)); |
133 | 48 | if (OneFeature != Features.back()) |
134 | 8 | MAttrString.append(","); |
135 | 48 | } |
136 | 295 | if (!Features.empty()) |
137 | 40 | LldArgs.push_back(Args.MakeArgString(MAttrString)); |
138 | | |
139 | | // ToDo: Remove this option after AMDGPU backend supports ISA-level linking. |
140 | | // Since AMDGPU backend currently does not support ISA-level linking, all |
141 | | // called functions need to be imported. |
142 | 295 | if (IsThinLTO) |
143 | 2 | LldArgs.push_back(Args.MakeArgString("-plugin-opt=-force-import-all")); |
144 | | |
145 | 295 | if (C.getDriver().isSaveTempsEnabled()) |
146 | 19 | LldArgs.push_back("-save-temps"); |
147 | | |
148 | 295 | addLinkerCompressDebugSectionsOption(TC, Args, LldArgs); |
149 | | |
150 | | // Given that host and device linking happen in separate processes, the device |
151 | | // linker doesn't always have the visibility as to which device symbols are |
152 | | // needed by a program, especially for the device symbol dependencies that are |
153 | | // introduced through the host symbol resolution. |
154 | | // For example: host_A() (A.obj) --> host_B(B.obj) --> device_kernel_B() |
155 | | // (B.obj) In this case, the device linker doesn't know that A.obj actually |
156 | | // depends on the kernel functions in B.obj. When linking to static device |
157 | | // library, the device linker may drop some of the device global symbols if |
158 | | // they aren't referenced. As a workaround, we are adding to the |
159 | | // --whole-archive flag such that all global symbols would be linked in. |
160 | 295 | LldArgs.push_back("--whole-archive"); |
161 | | |
162 | 295 | for (auto *Arg : Args.filtered(options::OPT_Xoffload_linker)) { |
163 | 11 | StringRef ArgVal = Arg->getValue(1); |
164 | 11 | auto SplitArg = ArgVal.split("-mllvm="); |
165 | 11 | if (!SplitArg.second.empty()) { |
166 | 8 | LldArgs.push_back( |
167 | 8 | Args.MakeArgString(Twine("-plugin-opt=") + SplitArg.second)); |
168 | 8 | } else { |
169 | 3 | LldArgs.push_back(Args.MakeArgString(ArgVal)); |
170 | 3 | } |
171 | 11 | Arg->claim(); |
172 | 11 | } |
173 | | |
174 | 295 | LldArgs.append({"-o", Output.getFilename()}); |
175 | 295 | for (auto Input : Inputs) |
176 | 315 | LldArgs.push_back(Input.getFilename()); |
177 | | |
178 | | // Look for archive of bundled bitcode in arguments, and add temporary files |
179 | | // for the extracted archive of bitcode to inputs. |
180 | 295 | auto TargetID = Args.getLastArgValue(options::OPT_mcpu_EQ); |
181 | 295 | AddStaticDeviceLibsLinking(C, *this, JA, Inputs, Args, LldArgs, "amdgcn", |
182 | 295 | TargetID, |
183 | 295 | /*IsBitCodeSDL=*/true, |
184 | 295 | /*PostClangLink=*/false); |
185 | | |
186 | 295 | LldArgs.push_back("--no-whole-archive"); |
187 | | |
188 | 295 | const char *Lld = Args.MakeArgString(getToolChain().GetProgramPath("lld")); |
189 | 295 | C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(), |
190 | 295 | Lld, LldArgs, Inputs, Output)); |
191 | 295 | } |
192 | | |
193 | | // For amdgcn the inputs of the linker job are device bitcode and output is |
194 | | // either an object file or bitcode (-emit-llvm). It calls llvm-link, opt, |
195 | | // llc, then lld steps. |
196 | | void AMDGCN::Linker::ConstructJob(Compilation &C, const JobAction &JA, |
197 | | const InputInfo &Output, |
198 | | const InputInfoList &Inputs, |
199 | | const ArgList &Args, |
200 | 521 | const char *LinkingOutput) const { |
201 | 521 | if (Inputs.size() > 0 && |
202 | 521 | Inputs[0].getType() == types::TY_Image && |
203 | 521 | JA.getType() == types::TY_Object224 ) |
204 | 45 | return HIP::constructGenerateObjFileFromHIPFatBinary(C, Output, Inputs, |
205 | 45 | Args, JA, *this); |
206 | | |
207 | 476 | if (JA.getType() == types::TY_HIP_FATBIN) |
208 | 179 | return HIP::constructHIPFatbinCommand(C, JA, Output.getFilename(), Inputs, |
209 | 179 | Args, *this); |
210 | | |
211 | 297 | if (JA.getType() == types::TY_LLVM_BC) |
212 | 2 | return constructLlvmLinkCommand(C, JA, Inputs, Output, Args); |
213 | | |
214 | 295 | return constructLldCommand(C, JA, Inputs, Output, Args); |
215 | 297 | } |
216 | | |
217 | | HIPAMDToolChain::HIPAMDToolChain(const Driver &D, const llvm::Triple &Triple, |
218 | | const ToolChain &HostTC, const ArgList &Args) |
219 | 376 | : ROCMToolChain(D, Triple, Args), HostTC(HostTC) { |
220 | | // Lookup binaries into the driver directory, this is used to |
221 | | // discover the clang-offload-bundler executable. |
222 | 376 | getProgramPaths().push_back(getDriver().Dir); |
223 | | |
224 | | // Diagnose unsupported sanitizer options only once. |
225 | 376 | if (!Args.hasFlag(options::OPT_fgpu_sanitize, options::OPT_fno_gpu_sanitize, |
226 | 376 | true)) |
227 | 3 | return; |
228 | 373 | for (auto *A : Args.filtered(options::OPT_fsanitize_EQ)) { |
229 | 8 | SanitizerMask K = parseSanitizerValue(A->getValue(), /*AllowGroups=*/false); |
230 | 8 | if (K != SanitizerKind::Address) |
231 | 2 | D.getDiags().Report(clang::diag::warn_drv_unsupported_option_for_target) |
232 | 2 | << A->getAsString(Args) << getTriple().str(); |
233 | 8 | } |
234 | 373 | } |
235 | | |
236 | | void HIPAMDToolChain::addClangTargetOptions( |
237 | | const llvm::opt::ArgList &DriverArgs, llvm::opt::ArgStringList &CC1Args, |
238 | 427 | Action::OffloadKind DeviceOffloadingKind) const { |
239 | 427 | HostTC.addClangTargetOptions(DriverArgs, CC1Args, DeviceOffloadingKind); |
240 | | |
241 | 427 | assert(DeviceOffloadingKind == Action::OFK_HIP && |
242 | 427 | "Only HIP offloading kinds are supported for GPUs."); |
243 | | |
244 | 427 | CC1Args.push_back("-fcuda-is-device"); |
245 | | |
246 | 427 | if (!DriverArgs.hasFlag(options::OPT_fgpu_rdc, options::OPT_fno_gpu_rdc, |
247 | 427 | false)) |
248 | 306 | CC1Args.append({"-mllvm", "-amdgpu-internalize-symbols"}); |
249 | | |
250 | 427 | StringRef MaxThreadsPerBlock = |
251 | 427 | DriverArgs.getLastArgValue(options::OPT_gpu_max_threads_per_block_EQ); |
252 | 427 | if (!MaxThreadsPerBlock.empty()) { |
253 | 1 | std::string ArgStr = |
254 | 1 | (Twine("--gpu-max-threads-per-block=") + MaxThreadsPerBlock).str(); |
255 | 1 | CC1Args.push_back(DriverArgs.MakeArgStringRef(ArgStr)); |
256 | 1 | } |
257 | | |
258 | 427 | CC1Args.push_back("-fcuda-allow-variadic-functions"); |
259 | | |
260 | | // Default to "hidden" visibility, as object level linking will not be |
261 | | // supported for the foreseeable future. |
262 | 427 | if (!DriverArgs.hasArg(options::OPT_fvisibility_EQ, |
263 | 427 | options::OPT_fvisibility_ms_compat)) { |
264 | 427 | CC1Args.append({"-fvisibility=hidden"}); |
265 | 427 | CC1Args.push_back("-fapply-global-visibility-to-externs"); |
266 | 427 | } |
267 | | |
268 | 753 | for (auto BCFile : getDeviceLibs(DriverArgs)) { |
269 | 753 | CC1Args.push_back(BCFile.ShouldInternalize ? "-mlink-builtin-bitcode"748 |
270 | 753 | : "-mlink-bitcode-file"5 ); |
271 | 753 | CC1Args.push_back(DriverArgs.MakeArgString(BCFile.Path)); |
272 | 753 | } |
273 | 427 | } |
274 | | |
275 | | llvm::opt::DerivedArgList * |
276 | | HIPAMDToolChain::TranslateArgs(const llvm::opt::DerivedArgList &Args, |
277 | | StringRef BoundArch, |
278 | 872 | Action::OffloadKind DeviceOffloadKind) const { |
279 | 872 | DerivedArgList *DAL = |
280 | 872 | HostTC.TranslateArgs(Args, BoundArch, DeviceOffloadKind); |
281 | 872 | if (!DAL) |
282 | 747 | DAL = new DerivedArgList(Args.getBaseArgs()); |
283 | | |
284 | 872 | const OptTable &Opts = getDriver().getOpts(); |
285 | | |
286 | 8.94k | for (Arg *A : Args) { |
287 | 8.94k | if (!shouldSkipSanitizeOption(*this, Args, BoundArch, A)) |
288 | 8.92k | DAL->append(A); |
289 | 8.94k | } |
290 | | |
291 | 872 | if (!BoundArch.empty()) { |
292 | 408 | DAL->eraseArg(options::OPT_mcpu_EQ); |
293 | 408 | DAL->AddJoinedArg(nullptr, Opts.getOption(options::OPT_mcpu_EQ), BoundArch); |
294 | 408 | checkTargetID(*DAL); |
295 | 408 | } |
296 | | |
297 | 872 | return DAL; |
298 | 872 | } |
299 | | |
300 | 228 | Tool *HIPAMDToolChain::buildLinker() const { |
301 | 228 | assert(getTriple().getArch() == llvm::Triple::amdgcn); |
302 | 228 | return new tools::AMDGCN::Linker(*this); |
303 | 228 | } |
304 | | |
305 | 427 | void HIPAMDToolChain::addClangWarningOptions(ArgStringList &CC1Args) const { |
306 | 427 | HostTC.addClangWarningOptions(CC1Args); |
307 | 427 | } |
308 | | |
309 | | ToolChain::CXXStdlibType |
310 | 0 | HIPAMDToolChain::GetCXXStdlibType(const ArgList &Args) const { |
311 | 0 | return HostTC.GetCXXStdlibType(Args); |
312 | 0 | } |
313 | | |
314 | | void HIPAMDToolChain::AddClangSystemIncludeArgs(const ArgList &DriverArgs, |
315 | 622 | ArgStringList &CC1Args) const { |
316 | 622 | HostTC.AddClangSystemIncludeArgs(DriverArgs, CC1Args); |
317 | 622 | } |
318 | | |
319 | | void HIPAMDToolChain::AddClangCXXStdlibIncludeArgs( |
320 | 622 | const ArgList &Args, ArgStringList &CC1Args) const { |
321 | 622 | HostTC.AddClangCXXStdlibIncludeArgs(Args, CC1Args); |
322 | 622 | } |
323 | | |
324 | | void HIPAMDToolChain::AddIAMCUIncludeArgs(const ArgList &Args, |
325 | 0 | ArgStringList &CC1Args) const { |
326 | 0 | HostTC.AddIAMCUIncludeArgs(Args, CC1Args); |
327 | 0 | } |
328 | | |
329 | | void HIPAMDToolChain::AddHIPIncludeArgs(const ArgList &DriverArgs, |
330 | 389 | ArgStringList &CC1Args) const { |
331 | 389 | RocmInstallation->AddHIPIncludeArgs(DriverArgs, CC1Args); |
332 | 389 | } |
333 | | |
334 | 508 | SanitizerMask HIPAMDToolChain::getSupportedSanitizers() const { |
335 | | // The HIPAMDToolChain only supports sanitizers in the sense that it allows |
336 | | // sanitizer arguments on the command line if they are supported by the host |
337 | | // toolchain. The HIPAMDToolChain will actually ignore any command line |
338 | | // arguments for any of these "supported" sanitizers. That means that no |
339 | | // sanitization of device code is actually supported at this time. |
340 | | // |
341 | | // This behavior is necessary because the host and device toolchains |
342 | | // invocations often share the command line, so the device toolchain must |
343 | | // tolerate flags meant only for the host toolchain. |
344 | 508 | return HostTC.getSupportedSanitizers(); |
345 | 508 | } |
346 | | |
347 | | VersionTuple HIPAMDToolChain::computeMSVCVersion(const Driver *D, |
348 | 427 | const ArgList &Args) const { |
349 | 427 | return HostTC.computeMSVCVersion(D, Args); |
350 | 427 | } |
351 | | |
352 | | llvm::SmallVector<ToolChain::BitCodeLibraryInfo, 12> |
353 | 427 | HIPAMDToolChain::getDeviceLibs(const llvm::opt::ArgList &DriverArgs) const { |
354 | 427 | llvm::SmallVector<BitCodeLibraryInfo, 12> BCLibs; |
355 | 427 | if (DriverArgs.hasArg(options::OPT_nogpulib)) |
356 | 304 | return {}; |
357 | 123 | ArgStringList LibraryPaths; |
358 | | |
359 | | // Find in --hip-device-lib-path and HIP_LIBRARY_PATH. |
360 | 123 | for (StringRef Path : RocmInstallation->getRocmDeviceLibPathArg()) |
361 | 64 | LibraryPaths.push_back(DriverArgs.MakeArgString(Path)); |
362 | | |
363 | 123 | addDirectoryList(DriverArgs, LibraryPaths, "", "HIP_DEVICE_LIB_PATH"); |
364 | | |
365 | | // Maintain compatability with --hip-device-lib. |
366 | 123 | auto BCLibArgs = DriverArgs.getAllArgValues(options::OPT_hip_device_lib_EQ); |
367 | 123 | if (!BCLibArgs.empty()) { |
368 | 59 | llvm::for_each(BCLibArgs, [&](StringRef BCName) { |
369 | 59 | StringRef FullName; |
370 | 83 | for (StringRef LibraryPath : LibraryPaths) { |
371 | 83 | SmallString<128> Path(LibraryPath); |
372 | 83 | llvm::sys::path::append(Path, BCName); |
373 | 83 | FullName = Path; |
374 | 83 | if (llvm::sys::fs::exists(FullName)) { |
375 | 59 | BCLibs.push_back(FullName); |
376 | 59 | return; |
377 | 59 | } |
378 | 83 | } |
379 | 0 | getDriver().Diag(diag::err_drv_no_such_file) << BCName; |
380 | 0 | }); |
381 | 88 | } else { |
382 | 88 | if (!RocmInstallation->hasDeviceLibrary()) { |
383 | 0 | getDriver().Diag(diag::err_drv_no_rocm_device_lib) << 0; |
384 | 0 | return {}; |
385 | 0 | } |
386 | 88 | StringRef GpuArch = getGPUArch(DriverArgs); |
387 | 88 | assert(!GpuArch.empty() && "Must have an explicit GPU arch."); |
388 | | |
389 | | // If --hip-device-lib is not set, add the default bitcode libraries. |
390 | 88 | if (DriverArgs.hasFlag(options::OPT_fgpu_sanitize, |
391 | 88 | options::OPT_fno_gpu_sanitize, true) && |
392 | 88 | getSanitizerArgs(DriverArgs).needsAsanRt()81 ) { |
393 | 6 | auto AsanRTL = RocmInstallation->getAsanRTLPath(); |
394 | 6 | if (AsanRTL.empty()) { |
395 | 1 | unsigned DiagID = getDriver().getDiags().getCustomDiagID( |
396 | 1 | DiagnosticsEngine::Error, |
397 | 1 | "AMDGPU address sanitizer runtime library (asanrtl) is not found. " |
398 | 1 | "Please install ROCm device library which supports address " |
399 | 1 | "sanitizer"); |
400 | 1 | getDriver().Diag(DiagID); |
401 | 1 | return {}; |
402 | 1 | } else |
403 | 5 | BCLibs.emplace_back(AsanRTL, /*ShouldInternalize=*/false); |
404 | 6 | } |
405 | | |
406 | | // Add the HIP specific bitcode library. |
407 | 87 | BCLibs.push_back(RocmInstallation->getHIPPath()); |
408 | | |
409 | | // Add common device libraries like ocml etc. |
410 | 87 | for (StringRef N : getCommonDeviceLibNames(DriverArgs, GpuArch.str())) |
411 | 601 | BCLibs.emplace_back(N); |
412 | | |
413 | | // Add instrument lib. |
414 | 87 | auto InstLib = |
415 | 87 | DriverArgs.getLastArgValue(options::OPT_gpu_instrument_lib_EQ); |
416 | 87 | if (InstLib.empty()) |
417 | 86 | return BCLibs; |
418 | 1 | if (llvm::sys::fs::exists(InstLib)) |
419 | 1 | BCLibs.push_back(InstLib); |
420 | 0 | else |
421 | 0 | getDriver().Diag(diag::err_drv_no_such_file) << InstLib; |
422 | 1 | } |
423 | | |
424 | 36 | return BCLibs; |
425 | 123 | } |
426 | | |
427 | | void HIPAMDToolChain::checkTargetID( |
428 | 408 | const llvm::opt::ArgList &DriverArgs) const { |
429 | 408 | auto PTID = getParsedTargetID(DriverArgs); |
430 | 408 | if (PTID.OptionalTargetID && !PTID.OptionalGPUArch) { |
431 | 0 | getDriver().Diag(clang::diag::err_drv_bad_target_id) |
432 | 0 | << *PTID.OptionalTargetID; |
433 | 0 | } |
434 | 408 | } |