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 "CommonArgs.h"
11 #include "clang/Driver/Compilation.h"
12 #include "clang/Driver/DriverDiagnostic.h"
13 #include "clang/Driver/Job.h"
14 #include "llvm/ADT/StringSwitch.h"
15 #include "llvm/TargetParser/Triple.h"
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(Parts, "_");
73   if (Parts.size() != 3)
74     return std::nullopt;
75 
76   Triple::EnvironmentType Kind =
77       StringSwitch<Triple::EnvironmentType>(Parts[0])
78           .Case("ps", Triple::EnvironmentType::Pixel)
79           .Case("vs", Triple::EnvironmentType::Vertex)
80           .Case("gs", Triple::EnvironmentType::Geometry)
81           .Case("hs", Triple::EnvironmentType::Hull)
82           .Case("ds", Triple::EnvironmentType::Domain)
83           .Case("cs", Triple::EnvironmentType::Compute)
84           .Case("lib", Triple::EnvironmentType::Library)
85           .Case("ms", Triple::EnvironmentType::Mesh)
86           .Case("as", Triple::EnvironmentType::Amplification)
87           .Default(Triple::EnvironmentType::UnknownEnvironment);
88   if (Kind == Triple::EnvironmentType::UnknownEnvironment)
89     return std::nullopt;
90 
91   unsigned long long Major = 0;
92   if (llvm::getAsUnsignedInteger(Parts[1], 0, 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(Parts[2], 0, Minor))
99     return std::nullopt;
100 
101   // dxil-unknown-shadermodel-hull
102   llvm::Triple T;
103   T.setArch(Triple::ArchType::dxil);
104   T.setOSName(Triple::getOSTypeName(Triple::OSType::ShaderModel).str() +
105               VersionTuple(Major, Minor).getAsString());
106   T.setEnvironment(Kind);
107   if (isLegalShaderModel(T))
108     return T.getTriple();
109   else
110     return std::nullopt;
111 }
112 
113 bool isLegalValidatorVersion(StringRef ValVersionStr, const Driver &D) {
114   VersionTuple Version;
115   if (Version.tryParse(ValVersionStr) || Version.getBuild() ||
116       Version.getSubminor() || !Version.getMinor()) {
117     D.Diag(diag::err_drv_invalid_format_dxil_validator_version)
118         << ValVersionStr;
119     return false;
120   }
121 
122   uint64_t Major = Version.getMajor();
123   uint64_t Minor = *Version.getMinor();
124   if (Major == 0 && Minor != 0) {
125     D.Diag(diag::err_drv_invalid_empty_dxil_validator_version) << ValVersionStr;
126     return false;
127   }
128   VersionTuple MinVer(1, 0);
129   if (Version < MinVer) {
130     D.Diag(diag::err_drv_invalid_range_dxil_validator_version) << ValVersionStr;
131     return false;
132   }
133   return true;
134 }
135 
136 } // namespace
137 
138 void tools::hlsl::Validator::ConstructJob(Compilation &C, const JobAction &JA,
139                                           const InputInfo &Output,
140                                           const InputInfoList &Inputs,
141                                           const ArgList &Args,
142                                           const char *LinkingOutput) const {
143   std::string DxvPath = getToolChain().GetProgramPath("dxv");
144   assert(DxvPath != "dxv" && "cannot find dxv");
145 
146   ArgStringList CmdArgs;
147   assert(Inputs.size() == 1 && "Unable to handle multiple inputs.");
148   const InputInfo &Input = Inputs[0];
149   assert(Input.isFilename() && "Unexpected verify input");
150   // Grabbing the output of the earlier cc1 run.
151   CmdArgs.push_back(Input.getFilename());
152   // Use the same name as output.
153   CmdArgs.push_back("-o");
154   CmdArgs.push_back(Input.getFilename());
155 
156   const char *Exec = Args.MakeArgString(DxvPath);
157   C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(),
158                                          Exec, CmdArgs, Inputs, Input));
159 }
160 
161 /// DirectX Toolchain
162 HLSLToolChain::HLSLToolChain(const Driver &D, const llvm::Triple &Triple,
163                              const ArgList &Args)
164     : ToolChain(D, Triple, Args) {
165   if (Args.hasArg(options::OPT_dxc_validator_path_EQ))
166     getProgramPaths().push_back(
167         Args.getLastArgValue(options::OPT_dxc_validator_path_EQ).str());
168 }
169 
170 Tool *clang::driver::toolchains::HLSLToolChain::getTool(
171     Action::ActionClass AC) const {
172   switch (AC) {
173   case Action::BinaryAnalyzeJobClass:
174     if (!Validator)
175       Validator.reset(new tools::hlsl::Validator(*this));
176     return Validator.get();
177   default:
178     return ToolChain::getTool(AC);
179   }
180 }
181 
182 std::optional<std::string>
183 clang::driver::toolchains::HLSLToolChain::parseTargetProfile(
184     StringRef TargetProfile) {
185   return tryParseProfile(TargetProfile);
186 }
187 
188 DerivedArgList *
189 HLSLToolChain::TranslateArgs(const DerivedArgList &Args, StringRef BoundArch,
190                              Action::OffloadKind DeviceOffloadKind) const {
191   DerivedArgList *DAL = new DerivedArgList(Args.getBaseArgs());
192 
193   const OptTable &Opts = getDriver().getOpts();
194 
195   for (Arg *A : Args) {
196     if (A->getOption().getID() == options::OPT_dxil_validator_version) {
197       StringRef ValVerStr = A->getValue();
198       std::string ErrorMsg;
199       if (!isLegalValidatorVersion(ValVerStr, getDriver()))
200         continue;
201     }
202     if (A->getOption().getID() == options::OPT_dxc_entrypoint) {
203       DAL->AddSeparateArg(nullptr, Opts.getOption(options::OPT_hlsl_entrypoint),
204                           A->getValue());
205       A->claim();
206       continue;
207     }
208     if (A->getOption().getID() == options::OPT__SLASH_O) {
209       StringRef OStr = A->getValue();
210       if (OStr == "d") {
211         DAL->AddFlagArg(nullptr, Opts.getOption(options::OPT_O0));
212         A->claim();
213         continue;
214       } else {
215         DAL->AddJoinedArg(nullptr, Opts.getOption(options::OPT_O), OStr);
216         A->claim();
217         continue;
218       }
219     }
220     if (A->getOption().getID() == options::OPT_emit_pristine_llvm) {
221       // Translate fcgl into -S -emit-llvm and -disable-llvm-passes.
222       DAL->AddFlagArg(nullptr, Opts.getOption(options::OPT_S));
223       DAL->AddFlagArg(nullptr, Opts.getOption(options::OPT_emit_llvm));
224       DAL->AddFlagArg(nullptr,
225                       Opts.getOption(options::OPT_disable_llvm_passes));
226       A->claim();
227       continue;
228     }
229     DAL->append(A);
230   }
231 
232   if (DAL->hasArg(options::OPT_o)) {
233     // When run the whole pipeline.
234     if (!DAL->hasArg(options::OPT_emit_llvm))
235       // Emit obj if write to file.
236       DAL->AddFlagArg(nullptr, Opts.getOption(options::OPT_emit_obj));
237   } else
238     DAL->AddSeparateArg(nullptr, Opts.getOption(options::OPT_o), "-");
239 
240   // Add default validator version if not set.
241   // TODO: remove this once read validator version from validator.
242   if (!DAL->hasArg(options::OPT_dxil_validator_version)) {
243     const StringRef DefaultValidatorVer = "1.7";
244     DAL->AddSeparateArg(nullptr,
245                         Opts.getOption(options::OPT_dxil_validator_version),
246                         DefaultValidatorVer);
247   }
248   if (!DAL->hasArg(options::OPT_O_Group)) {
249     DAL->AddJoinedArg(nullptr, Opts.getOption(options::OPT_O), "3");
250   }
251   // FIXME: add validation for enable_16bit_types should be after HLSL 2018 and
252   // shader model 6.2.
253   // See: https://github.com/llvm/llvm-project/issues/57876
254   return DAL;
255 }
256 
257 bool HLSLToolChain::requiresValidation(DerivedArgList &Args) const {
258   if (Args.getLastArg(options::OPT_dxc_disable_validation))
259     return false;
260 
261   std::string DxvPath = GetProgramPath("dxv");
262   if (DxvPath != "dxv")
263     return true;
264 
265   getDriver().Diag(diag::warn_drv_dxc_missing_dxv);
266   return false;
267 }
268