1 | //===--- HLSL.cpp - HLSL 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 "HLSL.h" |
10 | #include "clang/Driver/CommonArgs.h" |
11 | #include "clang/Driver/Compilation.h" |
12 | #include "clang/Driver/Job.h" |
13 | #include "llvm/ADT/StringSwitch.h" |
14 | #include "llvm/TargetParser/Triple.h" |
15 | #include <regex> |
16 | |
17 | using namespace clang::driver; |
18 | using namespace clang::driver::tools; |
19 | using namespace clang::driver::toolchains; |
20 | using namespace clang; |
21 | using namespace llvm::opt; |
22 | using namespace llvm; |
23 | |
24 | namespace { |
25 | |
26 | const unsigned OfflineLibMinor = 0xF; |
27 | |
28 | bool isLegalShaderModel(Triple &T) { |
29 | if (T.getOS() != Triple::OSType::ShaderModel) |
30 | return false; |
31 | |
32 | auto Version = T.getOSVersion(); |
33 | if (Version.getBuild()) |
34 | return false; |
35 | if (Version.getSubminor()) |
36 | return false; |
37 | |
38 | auto Kind = T.getEnvironment(); |
39 | |
40 | switch (Kind) { |
41 | default: |
42 | return false; |
43 | case Triple::EnvironmentType::Vertex: |
44 | case Triple::EnvironmentType::Hull: |
45 | case Triple::EnvironmentType::Domain: |
46 | case Triple::EnvironmentType::Geometry: |
47 | case Triple::EnvironmentType::Pixel: |
48 | case Triple::EnvironmentType::Compute: { |
49 | VersionTuple MinVer(4, 0); |
50 | return MinVer <= Version; |
51 | } break; |
52 | case Triple::EnvironmentType::Library: { |
53 | VersionTuple SM6x(6, OfflineLibMinor); |
54 | if (Version == SM6x) |
55 | return true; |
56 | |
57 | VersionTuple MinVer(6, 3); |
58 | return MinVer <= Version; |
59 | } break; |
60 | case Triple::EnvironmentType::Amplification: |
61 | case Triple::EnvironmentType::Mesh: { |
62 | VersionTuple MinVer(6, 5); |
63 | return MinVer <= Version; |
64 | } break; |
65 | } |
66 | return false; |
67 | } |
68 | |
69 | std::optional<std::string> tryParseProfile(StringRef Profile) { |
70 | // [ps|vs|gs|hs|ds|cs|ms|as]_[major]_[minor] |
71 | SmallVector<StringRef, 3> Parts; |
72 | Profile.split(A&: Parts, Separator: "_" ); |
73 | if (Parts.size() != 3) |
74 | return std::nullopt; |
75 | |
76 | Triple::EnvironmentType Kind = |
77 | StringSwitch<Triple::EnvironmentType>(Parts[0]) |
78 | .Case(S: "ps" , Value: Triple::EnvironmentType::Pixel) |
79 | .Case(S: "vs" , Value: Triple::EnvironmentType::Vertex) |
80 | .Case(S: "gs" , Value: Triple::EnvironmentType::Geometry) |
81 | .Case(S: "hs" , Value: Triple::EnvironmentType::Hull) |
82 | .Case(S: "ds" , Value: Triple::EnvironmentType::Domain) |
83 | .Case(S: "cs" , Value: Triple::EnvironmentType::Compute) |
84 | .Case(S: "lib" , Value: Triple::EnvironmentType::Library) |
85 | .Case(S: "ms" , Value: Triple::EnvironmentType::Mesh) |
86 | .Case(S: "as" , Value: Triple::EnvironmentType::Amplification) |
87 | .Default(Value: Triple::EnvironmentType::UnknownEnvironment); |
88 | if (Kind == Triple::EnvironmentType::UnknownEnvironment) |
89 | return std::nullopt; |
90 | |
91 | unsigned long long Major = 0; |
92 | if (llvm::getAsUnsignedInteger(Str: Parts[1], Radix: 0, Result&: Major)) |
93 | return std::nullopt; |
94 | |
95 | unsigned long long Minor = 0; |
96 | if (Parts[2] == "x" && Kind == Triple::EnvironmentType::Library) |
97 | Minor = OfflineLibMinor; |
98 | else if (llvm::getAsUnsignedInteger(Str: Parts[2], Radix: 0, Result&: Minor)) |
99 | return std::nullopt; |
100 | |
101 | // Determine DXIL version using the minor version number of Shader |
102 | // Model version specified in target profile. Prior to decoupling DXIL version |
103 | // numbering from that of Shader Model DXIL version 1.Y corresponds to SM 6.Y. |
104 | // E.g., dxilv1.Y-unknown-shadermodelX.Y-hull |
105 | llvm::Triple T; |
106 | Triple::SubArchType SubArch = llvm::Triple::NoSubArch; |
107 | switch (Minor) { |
108 | case 0: |
109 | SubArch = llvm::Triple::DXILSubArch_v1_0; |
110 | break; |
111 | case 1: |
112 | SubArch = llvm::Triple::DXILSubArch_v1_1; |
113 | break; |
114 | case 2: |
115 | SubArch = llvm::Triple::DXILSubArch_v1_2; |
116 | break; |
117 | case 3: |
118 | SubArch = llvm::Triple::DXILSubArch_v1_3; |
119 | break; |
120 | case 4: |
121 | SubArch = llvm::Triple::DXILSubArch_v1_4; |
122 | break; |
123 | case 5: |
124 | SubArch = llvm::Triple::DXILSubArch_v1_5; |
125 | break; |
126 | case 6: |
127 | SubArch = llvm::Triple::DXILSubArch_v1_6; |
128 | break; |
129 | case 7: |
130 | SubArch = llvm::Triple::DXILSubArch_v1_7; |
131 | break; |
132 | case 8: |
133 | SubArch = llvm::Triple::DXILSubArch_v1_8; |
134 | break; |
135 | case OfflineLibMinor: |
136 | // Always consider minor version x as the latest supported DXIL version |
137 | SubArch = llvm::Triple::LatestDXILSubArch; |
138 | break; |
139 | default: |
140 | // No DXIL Version corresponding to specified Shader Model version found |
141 | return std::nullopt; |
142 | } |
143 | T.setArch(Kind: Triple::ArchType::dxil, SubArch); |
144 | T.setOSName(Triple::getOSTypeName(Kind: Triple::OSType::ShaderModel).str() + |
145 | VersionTuple(Major, Minor).getAsString()); |
146 | T.setEnvironment(Kind); |
147 | if (isLegalShaderModel(T)) |
148 | return T.getTriple(); |
149 | else |
150 | return std::nullopt; |
151 | } |
152 | |
153 | bool isLegalValidatorVersion(StringRef ValVersionStr, const Driver &D) { |
154 | VersionTuple Version; |
155 | if (Version.tryParse(string: ValVersionStr) || Version.getBuild() || |
156 | Version.getSubminor() || !Version.getMinor()) { |
157 | D.Diag(diag::DiagID: err_drv_invalid_format_dxil_validator_version) |
158 | << ValVersionStr; |
159 | return false; |
160 | } |
161 | |
162 | uint64_t Major = Version.getMajor(); |
163 | uint64_t Minor = *Version.getMinor(); |
164 | if (Major == 0 && Minor != 0) { |
165 | D.Diag(diag::DiagID: err_drv_invalid_empty_dxil_validator_version) << ValVersionStr; |
166 | return false; |
167 | } |
168 | VersionTuple MinVer(1, 0); |
169 | if (Version < MinVer) { |
170 | D.Diag(diag::DiagID: err_drv_invalid_range_dxil_validator_version) << ValVersionStr; |
171 | return false; |
172 | } |
173 | return true; |
174 | } |
175 | |
176 | std::string getSpirvExtArg(ArrayRef<std::string> SpvExtensionArgs) { |
177 | if (SpvExtensionArgs.empty()) { |
178 | return "-spirv-ext=all" ; |
179 | } |
180 | |
181 | std::string LlvmOption = |
182 | (Twine("-spirv-ext=+" ) + SpvExtensionArgs.front()).str(); |
183 | SpvExtensionArgs = SpvExtensionArgs.slice(N: 1); |
184 | for (auto Extension : SpvExtensionArgs) { |
185 | LlvmOption = (Twine(LlvmOption) + ",+" + Extension).str(); |
186 | } |
187 | return LlvmOption; |
188 | } |
189 | |
190 | bool isValidSPIRVExtensionName(const std::string &str) { |
191 | std::regex pattern("SPV_[a-zA-Z0-9_]+" ); |
192 | return std::regex_match(s: str, re: pattern); |
193 | } |
194 | |
195 | // SPIRV extension names are of the form `SPV_[a-zA-Z0-9_]+`. We want to |
196 | // disallow obviously invalid names to avoid issues when parsing `spirv-ext`. |
197 | bool checkExtensionArgsAreValid(ArrayRef<std::string> SpvExtensionArgs, |
198 | const Driver &Driver) { |
199 | bool AllValid = true; |
200 | for (auto Extension : SpvExtensionArgs) { |
201 | if (!isValidSPIRVExtensionName(str: Extension)) { |
202 | Driver.Diag(diag::DiagID: err_drv_invalid_value) |
203 | << "-fspv_extension" << Extension; |
204 | AllValid = false; |
205 | } |
206 | } |
207 | return AllValid; |
208 | } |
209 | } // namespace |
210 | |
211 | void tools::hlsl::Validator::ConstructJob(Compilation &C, const JobAction &JA, |
212 | const InputInfo &Output, |
213 | const InputInfoList &Inputs, |
214 | const ArgList &Args, |
215 | const char *LinkingOutput) const { |
216 | std::string DxvPath = getToolChain().GetProgramPath(Name: "dxv" ); |
217 | assert(DxvPath != "dxv" && "cannot find dxv" ); |
218 | |
219 | ArgStringList CmdArgs; |
220 | assert(Inputs.size() == 1 && "Unable to handle multiple inputs." ); |
221 | const InputInfo &Input = Inputs[0]; |
222 | CmdArgs.push_back(Elt: Input.getFilename()); |
223 | CmdArgs.push_back(Elt: "-o" ); |
224 | CmdArgs.push_back(Elt: Output.getFilename()); |
225 | |
226 | const char *Exec = Args.MakeArgString(Str: DxvPath); |
227 | C.addCommand(C: std::make_unique<Command>(args: JA, args: *this, args: ResponseFileSupport::None(), |
228 | args&: Exec, args&: CmdArgs, args: Inputs, args: Input)); |
229 | } |
230 | |
231 | void tools::hlsl::MetalConverter::ConstructJob( |
232 | Compilation &C, const JobAction &JA, const InputInfo &Output, |
233 | const InputInfoList &Inputs, const ArgList &Args, |
234 | const char *LinkingOutput) const { |
235 | std::string MSCPath = getToolChain().GetProgramPath(Name: "metal-shaderconverter" ); |
236 | ArgStringList CmdArgs; |
237 | assert(Inputs.size() == 1 && "Unable to handle multiple inputs." ); |
238 | const InputInfo &Input = Inputs[0]; |
239 | CmdArgs.push_back(Elt: Input.getFilename()); |
240 | CmdArgs.push_back(Elt: "-o" ); |
241 | CmdArgs.push_back(Elt: Output.getFilename()); |
242 | |
243 | const char *Exec = Args.MakeArgString(Str: MSCPath); |
244 | C.addCommand(C: std::make_unique<Command>(args: JA, args: *this, args: ResponseFileSupport::None(), |
245 | args&: Exec, args&: CmdArgs, args: Inputs, args: Input)); |
246 | } |
247 | |
248 | /// DirectX Toolchain |
249 | HLSLToolChain::HLSLToolChain(const Driver &D, const llvm::Triple &Triple, |
250 | const ArgList &Args) |
251 | : ToolChain(D, Triple, Args) { |
252 | if (Args.hasArg(options::OPT_dxc_validator_path_EQ)) |
253 | getProgramPaths().push_back( |
254 | Args.getLastArgValue(options::Id: OPT_dxc_validator_path_EQ).str()); |
255 | } |
256 | |
257 | Tool *clang::driver::toolchains::HLSLToolChain::getTool( |
258 | Action::ActionClass AC) const { |
259 | switch (AC) { |
260 | case Action::BinaryAnalyzeJobClass: |
261 | if (!Validator) |
262 | Validator.reset(p: new tools::hlsl::Validator(*this)); |
263 | return Validator.get(); |
264 | case Action::BinaryTranslatorJobClass: |
265 | if (!MetalConverter) |
266 | MetalConverter.reset(p: new tools::hlsl::MetalConverter(*this)); |
267 | return MetalConverter.get(); |
268 | default: |
269 | return ToolChain::getTool(AC); |
270 | } |
271 | } |
272 | |
273 | std::optional<std::string> |
274 | clang::driver::toolchains::HLSLToolChain::parseTargetProfile( |
275 | StringRef TargetProfile) { |
276 | return tryParseProfile(Profile: TargetProfile); |
277 | } |
278 | |
279 | DerivedArgList * |
280 | HLSLToolChain::TranslateArgs(const DerivedArgList &Args, StringRef BoundArch, |
281 | Action::OffloadKind DeviceOffloadKind) const { |
282 | DerivedArgList *DAL = new DerivedArgList(Args.getBaseArgs()); |
283 | |
284 | const OptTable &Opts = getDriver().getOpts(); |
285 | |
286 | for (Arg *A : Args) { |
287 | if (A->getOption().getID() == options::OPT_dxil_validator_version) { |
288 | StringRef ValVerStr = A->getValue(); |
289 | if (!isLegalValidatorVersion(ValVersionStr: ValVerStr, D: getDriver())) |
290 | continue; |
291 | } |
292 | if (A->getOption().getID() == options::OPT_dxc_entrypoint) { |
293 | DAL->AddSeparateArg(BaseArg: nullptr, Opt: Opts.getOption(options::Opt: OPT_hlsl_entrypoint), |
294 | Value: A->getValue()); |
295 | A->claim(); |
296 | continue; |
297 | } |
298 | if (A->getOption().getID() == options::OPT__SLASH_O) { |
299 | StringRef OStr = A->getValue(); |
300 | if (OStr == "d" ) { |
301 | DAL->AddFlagArg(BaseArg: nullptr, Opt: Opts.getOption(options::Opt: OPT_O0)); |
302 | A->claim(); |
303 | continue; |
304 | } else { |
305 | DAL->AddJoinedArg(BaseArg: nullptr, Opt: Opts.getOption(options::Opt: OPT_O), Value: OStr); |
306 | A->claim(); |
307 | continue; |
308 | } |
309 | } |
310 | if (A->getOption().getID() == options::OPT_emit_pristine_llvm) { |
311 | // Translate -fcgl into -emit-llvm and -disable-llvm-passes. |
312 | DAL->AddFlagArg(BaseArg: nullptr, Opt: Opts.getOption(options::Opt: OPT_emit_llvm)); |
313 | DAL->AddFlagArg(BaseArg: nullptr, |
314 | Opt: Opts.getOption(options::Opt: OPT_disable_llvm_passes)); |
315 | A->claim(); |
316 | continue; |
317 | } |
318 | if (A->getOption().getID() == options::OPT_dxc_hlsl_version) { |
319 | // Translate -HV into -std for llvm |
320 | // depending on the value given |
321 | LangStandard::Kind LangStd = LangStandard::getHLSLLangKind(Name: A->getValue()); |
322 | if (LangStd != LangStandard::lang_unspecified) { |
323 | LangStandard l = LangStandard::getLangStandardForKind(K: LangStd); |
324 | DAL->AddSeparateArg(BaseArg: nullptr, Opt: Opts.getOption(options::Opt: OPT_std_EQ), |
325 | Value: l.getName()); |
326 | } else { |
327 | getDriver().Diag(diag::DiagID: err_drv_invalid_value) << "HV" << A->getValue(); |
328 | } |
329 | |
330 | A->claim(); |
331 | continue; |
332 | } |
333 | DAL->append(A); |
334 | } |
335 | |
336 | if (getArch() == llvm::Triple::spirv) { |
337 | std::vector<std::string> SpvExtensionArgs = |
338 | Args.getAllArgValues(options::Id: OPT_fspv_extension_EQ); |
339 | if (checkExtensionArgsAreValid(SpvExtensionArgs, Driver: getDriver())) { |
340 | std::string LlvmOption = getSpirvExtArg(SpvExtensionArgs); |
341 | DAL->AddSeparateArg(BaseArg: nullptr, Opt: Opts.getOption(options::Opt: OPT_mllvm), |
342 | Value: LlvmOption); |
343 | } |
344 | Args.claimAllArgs(options::OPT_fspv_extension_EQ); |
345 | } |
346 | |
347 | if (!DAL->hasArg(options::OPT_O_Group)) { |
348 | DAL->AddJoinedArg(BaseArg: nullptr, Opt: Opts.getOption(options::Opt: OPT_O), Value: "3" ); |
349 | } |
350 | |
351 | return DAL; |
352 | } |
353 | |
354 | bool HLSLToolChain::requiresValidation(DerivedArgList &Args) const { |
355 | if (!Args.hasArg(options::OPT_dxc_Fo)) |
356 | return false; |
357 | |
358 | if (Args.getLastArg(options::OPT_dxc_disable_validation)) |
359 | return false; |
360 | |
361 | std::string DxvPath = GetProgramPath(Name: "dxv" ); |
362 | if (DxvPath != "dxv" ) |
363 | return true; |
364 | |
365 | getDriver().Diag(diag::DiagID: warn_drv_dxc_missing_dxv); |
366 | return false; |
367 | } |
368 | |
369 | bool HLSLToolChain::requiresBinaryTranslation(DerivedArgList &Args) const { |
370 | return Args.hasArg(options::OPT_metal) && Args.hasArg(options::OPT_dxc_Fo); |
371 | } |
372 | |
373 | bool HLSLToolChain::isLastJob(DerivedArgList &Args, |
374 | Action::ActionClass AC) const { |
375 | bool HasTranslation = requiresBinaryTranslation(Args); |
376 | bool HasValidation = requiresValidation(Args); |
377 | // If translation and validation are not required, we should only have one |
378 | // action. |
379 | if (!HasTranslation && !HasValidation) |
380 | return true; |
381 | if ((HasTranslation && AC == Action::BinaryTranslatorJobClass) || |
382 | (!HasTranslation && HasValidation && AC == Action::BinaryAnalyzeJobClass)) |
383 | return true; |
384 | return false; |
385 | } |
386 | |