574 lines · cpp
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-exception6//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 17using namespace clang::driver;18using namespace clang::driver::tools;19using namespace clang::driver::toolchains;20using namespace clang;21using namespace llvm::opt;22using namespace llvm;23 24namespace {25 26const unsigned OfflineLibMinor = 0xF;27 28bool 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 case Triple::EnvironmentType::RootSignature:66 VersionTuple MinVer(1, 0);67 VersionTuple MaxVer(1, 2);68 return MinVer <= Version && Version <= MaxVer;69 }70 return false;71}72 73std::optional<llvm::Triple> tryParseTriple(StringRef Profile) {74 // [ps|vs|gs|hs|ds|cs|ms|as]_[major]_[minor]75 SmallVector<StringRef, 3> Parts;76 Profile.split(Parts, "_");77 if (Parts.size() != 3)78 return std::nullopt;79 80 Triple::EnvironmentType Kind =81 StringSwitch<Triple::EnvironmentType>(Parts[0])82 .Case("ps", Triple::EnvironmentType::Pixel)83 .Case("vs", Triple::EnvironmentType::Vertex)84 .Case("gs", Triple::EnvironmentType::Geometry)85 .Case("hs", Triple::EnvironmentType::Hull)86 .Case("ds", Triple::EnvironmentType::Domain)87 .Case("cs", Triple::EnvironmentType::Compute)88 .Case("lib", Triple::EnvironmentType::Library)89 .Case("ms", Triple::EnvironmentType::Mesh)90 .Case("as", Triple::EnvironmentType::Amplification)91 .Case("rootsig", Triple::EnvironmentType::RootSignature)92 .Default(Triple::EnvironmentType::UnknownEnvironment);93 if (Kind == Triple::EnvironmentType::UnknownEnvironment)94 return std::nullopt;95 96 unsigned long long Major = 0;97 if (llvm::getAsUnsignedInteger(Parts[1], 0, Major))98 return std::nullopt;99 100 unsigned long long Minor = 0;101 if (Parts[2] == "x" && Kind == Triple::EnvironmentType::Library)102 Minor = OfflineLibMinor;103 else if (llvm::getAsUnsignedInteger(Parts[2], 0, Minor))104 return std::nullopt;105 106 // Determine DXIL version using the minor version number of Shader107 // Model version specified in target profile. Prior to decoupling DXIL version108 // numbering from that of Shader Model DXIL version 1.Y corresponds to SM 6.Y.109 // E.g., dxilv1.Y-unknown-shadermodelX.Y-hull110 llvm::Triple T;111 Triple::SubArchType SubArch = llvm::Triple::NoSubArch;112 switch (Minor) {113 case 0:114 SubArch = llvm::Triple::DXILSubArch_v1_0;115 break;116 case 1:117 SubArch = llvm::Triple::DXILSubArch_v1_1;118 break;119 case 2:120 SubArch = llvm::Triple::DXILSubArch_v1_2;121 break;122 case 3:123 SubArch = llvm::Triple::DXILSubArch_v1_3;124 break;125 case 4:126 SubArch = llvm::Triple::DXILSubArch_v1_4;127 break;128 case 5:129 SubArch = llvm::Triple::DXILSubArch_v1_5;130 break;131 case 6:132 SubArch = llvm::Triple::DXILSubArch_v1_6;133 break;134 case 7:135 SubArch = llvm::Triple::DXILSubArch_v1_7;136 break;137 case 8:138 SubArch = llvm::Triple::DXILSubArch_v1_8;139 break;140 case 9:141 SubArch = llvm::Triple::DXILSubArch_v1_9;142 break;143 case OfflineLibMinor:144 // Always consider minor version x as the latest supported DXIL version145 SubArch = llvm::Triple::LatestDXILSubArch;146 break;147 default:148 // No DXIL Version corresponding to specified Shader Model version found149 return std::nullopt;150 }151 T.setArch(Triple::ArchType::dxil, SubArch);152 T.setOSName(Triple::getOSTypeName(Triple::OSType::ShaderModel).str() +153 VersionTuple(Major, Minor).getAsString());154 T.setEnvironment(Kind);155 156 return T;157}158 159std::optional<std::string> tryParseProfile(StringRef Profile) {160 std::optional<llvm::Triple> MaybeT = tryParseTriple(Profile);161 if (MaybeT && isLegalShaderModel(*MaybeT))162 return MaybeT->getTriple();163 else164 return std::nullopt;165}166 167bool isLegalValidatorVersion(StringRef ValVersionStr, const Driver &D) {168 VersionTuple Version;169 if (Version.tryParse(ValVersionStr) || Version.getBuild() ||170 Version.getSubminor() || !Version.getMinor()) {171 D.Diag(diag::err_drv_invalid_format_dxil_validator_version)172 << ValVersionStr;173 return false;174 }175 176 uint64_t Major = Version.getMajor();177 uint64_t Minor = *Version.getMinor();178 if (Major == 0 && Minor != 0) {179 D.Diag(diag::err_drv_invalid_empty_dxil_validator_version) << ValVersionStr;180 return false;181 }182 VersionTuple MinVer(1, 0);183 if (Version < MinVer) {184 D.Diag(diag::err_drv_invalid_range_dxil_validator_version) << ValVersionStr;185 return false;186 }187 return true;188}189 190void getSpirvExtOperand(StringRef SpvExtensionArg, raw_ostream &out) {191 // The extensions that are commented out are supported in DXC, but the SPIR-V192 // backend does not know about them yet.193 static const std::vector<StringRef> DxcSupportedExtensions = {194 "SPV_KHR_16bit_storage",195 "SPV_KHR_device_group",196 "SPV_KHR_fragment_shading_rate",197 "SPV_KHR_multiview",198 "SPV_KHR_post_depth_coverage",199 "SPV_KHR_non_semantic_info",200 "SPV_KHR_shader_draw_parameters",201 "SPV_KHR_ray_tracing",202 "SPV_KHR_shader_clock",203 "SPV_EXT_demote_to_helper_invocation",204 "SPV_EXT_descriptor_indexing",205 "SPV_EXT_fragment_fully_covered",206 "SPV_EXT_fragment_invocation_density",207 "SPV_EXT_fragment_shader_interlock",208 "SPV_EXT_mesh_shader",209 "SPV_EXT_shader_stencil_export",210 "SPV_EXT_shader_viewport_index_layer",211 // "SPV_AMD_shader_early_and_late_fragment_tests",212 "SPV_GOOGLE_hlsl_functionality1",213 "SPV_GOOGLE_user_type",214 "SPV_KHR_ray_query",215 "SPV_EXT_shader_image_int64",216 "SPV_KHR_fragment_shader_barycentric",217 "SPV_KHR_physical_storage_buffer",218 "SPV_KHR_vulkan_memory_model",219 // "SPV_KHR_compute_shader_derivatives",220 "SPV_KHR_maximal_reconvergence",221 "SPV_KHR_float_controls",222 "SPV_NV_shader_subgroup_partitioned",223 // "SPV_KHR_quad_control"224 };225 226 if (SpvExtensionArg.starts_with("SPV_")) {227 out << "+" << SpvExtensionArg;228 return;229 }230 231 if (SpvExtensionArg.compare_insensitive("DXC") == 0) {232 bool first = true;233 for (StringRef E : DxcSupportedExtensions) {234 if (!first)235 out << ",";236 else237 first = false;238 out << "+" << E;239 }240 return;241 }242 out << SpvExtensionArg;243}244 245SmallString<1024> getSpirvExtArg(ArrayRef<std::string> SpvExtensionArgs) {246 if (SpvExtensionArgs.empty()) {247 return StringRef("-spirv-ext=all");248 }249 250 llvm::SmallString<1024> LlvmOption;251 raw_svector_ostream out(LlvmOption);252 253 out << "-spirv-ext=";254 getSpirvExtOperand(SpvExtensionArgs[0], out);255 256 SpvExtensionArgs = SpvExtensionArgs.slice(1);257 for (StringRef Extension : SpvExtensionArgs) {258 out << ",";259 getSpirvExtOperand(Extension, out);260 }261 return LlvmOption;262}263 264bool isValidSPIRVExtensionName(const std::string &str) {265 std::regex pattern("dxc|DXC|khr|KHR|SPV_[a-zA-Z0-9_]+");266 return std::regex_match(str, pattern);267}268 269// SPIRV extension names are of the form `SPV_[a-zA-Z0-9_]+`. We want to270// disallow obviously invalid names to avoid issues when parsing `spirv-ext`.271bool checkExtensionArgsAreValid(ArrayRef<std::string> SpvExtensionArgs,272 const Driver &Driver) {273 bool AllValid = true;274 for (auto Extension : SpvExtensionArgs) {275 if (!isValidSPIRVExtensionName(Extension)) {276 Driver.Diag(diag::err_drv_invalid_value)277 << "-fspv-extension" << Extension;278 AllValid = false;279 }280 }281 return AllValid;282}283 284bool isRootSignatureTarget(StringRef Profile) {285 if (std::optional<llvm::Triple> T = tryParseTriple(Profile))286 return T->getEnvironment() == Triple::EnvironmentType::RootSignature;287 return false;288}289 290bool isRootSignatureTarget(DerivedArgList &Args) {291 if (const Arg *A = Args.getLastArg(options::OPT_target_profile))292 return isRootSignatureTarget(A->getValue());293 return false;294}295 296} // namespace297 298void tools::hlsl::Validator::ConstructJob(Compilation &C, const JobAction &JA,299 const InputInfo &Output,300 const InputInfoList &Inputs,301 const ArgList &Args,302 const char *LinkingOutput) const {303 std::string DxvPath = getToolChain().GetProgramPath("dxv");304 assert(DxvPath != "dxv" && "cannot find dxv");305 306 ArgStringList CmdArgs;307 assert(Inputs.size() == 1 && "Unable to handle multiple inputs.");308 const InputInfo &Input = Inputs[0];309 CmdArgs.push_back(Input.getFilename());310 CmdArgs.push_back("-o");311 CmdArgs.push_back(Output.getFilename());312 313 const char *Exec = Args.MakeArgString(DxvPath);314 C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(),315 Exec, CmdArgs, Inputs, Input));316}317 318void tools::hlsl::MetalConverter::ConstructJob(319 Compilation &C, const JobAction &JA, const InputInfo &Output,320 const InputInfoList &Inputs, const ArgList &Args,321 const char *LinkingOutput) const {322 std::string MSCPath = getToolChain().GetProgramPath("metal-shaderconverter");323 ArgStringList CmdArgs;324 assert(Inputs.size() == 1 && "Unable to handle multiple inputs.");325 const InputInfo &Input = Inputs[0];326 CmdArgs.push_back(Input.getFilename());327 CmdArgs.push_back("-o");328 CmdArgs.push_back(Output.getFilename());329 330 const char *Exec = Args.MakeArgString(MSCPath);331 C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(),332 Exec, CmdArgs, Inputs, Input));333}334 335void tools::hlsl::LLVMObjcopy::ConstructJob(Compilation &C, const JobAction &JA,336 const InputInfo &Output,337 const InputInfoList &Inputs,338 const ArgList &Args,339 const char *LinkingOutput) const {340 341 std::string ObjcopyPath = getToolChain().GetProgramPath("llvm-objcopy");342 const char *Exec = Args.MakeArgString(ObjcopyPath);343 344 ArgStringList CmdArgs;345 assert(Inputs.size() == 1 && "Unable to handle multiple inputs.");346 const InputInfo &Input = Inputs[0];347 CmdArgs.push_back(Input.getFilename());348 CmdArgs.push_back(Output.getFilename());349 350 if (Args.hasArg(options::OPT_dxc_strip_rootsignature)) {351 const char *StripRS = Args.MakeArgString("--remove-section=RTS0");352 CmdArgs.push_back(StripRS);353 }354 355 if (Arg *Arg = Args.getLastArg(options::OPT_dxc_Frs)) {356 const char *Frs =357 Args.MakeArgString("--extract-section=RTS0=" + Twine(Arg->getValue()));358 CmdArgs.push_back(Frs);359 }360 361 if (const Arg *A = Args.getLastArg(options::OPT_target_profile))362 if (isRootSignatureTarget(A->getValue())) {363 const char *Fos = Args.MakeArgString("--only-section=RTS0");364 CmdArgs.push_back(Fos);365 }366 367 assert(CmdArgs.size() > 2 && "Unnecessary invocation of objcopy.");368 369 C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(),370 Exec, CmdArgs, Inputs, Input));371}372 373/// DirectX Toolchain374HLSLToolChain::HLSLToolChain(const Driver &D, const llvm::Triple &Triple,375 const ArgList &Args)376 : ToolChain(D, Triple, Args) {377 if (Args.hasArg(options::OPT_dxc_validator_path_EQ))378 getProgramPaths().push_back(379 Args.getLastArgValue(options::OPT_dxc_validator_path_EQ).str());380}381 382Tool *clang::driver::toolchains::HLSLToolChain::getTool(383 Action::ActionClass AC) const {384 switch (AC) {385 case Action::BinaryAnalyzeJobClass:386 if (!Validator)387 Validator.reset(new tools::hlsl::Validator(*this));388 return Validator.get();389 case Action::BinaryTranslatorJobClass:390 if (!MetalConverter)391 MetalConverter.reset(new tools::hlsl::MetalConverter(*this));392 return MetalConverter.get();393 case Action::ObjcopyJobClass:394 if (!LLVMObjcopy)395 LLVMObjcopy.reset(new tools::hlsl::LLVMObjcopy(*this));396 return LLVMObjcopy.get();397 default:398 return ToolChain::getTool(AC);399 }400}401 402std::optional<std::string>403clang::driver::toolchains::HLSLToolChain::parseTargetProfile(404 StringRef TargetProfile) {405 return tryParseProfile(TargetProfile);406}407 408DerivedArgList *409HLSLToolChain::TranslateArgs(const DerivedArgList &Args, StringRef BoundArch,410 Action::OffloadKind DeviceOffloadKind) const {411 DerivedArgList *DAL = new DerivedArgList(Args.getBaseArgs());412 413 const OptTable &Opts = getDriver().getOpts();414 415 for (Arg *A : Args) {416 if (A->getOption().getID() == options::OPT_dxil_validator_version) {417 StringRef ValVerStr = A->getValue();418 if (!isLegalValidatorVersion(ValVerStr, getDriver()))419 continue;420 }421 if (A->getOption().getID() == options::OPT_dxc_entrypoint) {422 DAL->AddSeparateArg(nullptr, Opts.getOption(options::OPT_hlsl_entrypoint),423 A->getValue());424 A->claim();425 continue;426 }427 if (A->getOption().getID() == options::OPT_dxc_rootsig_ver) {428 DAL->AddJoinedArg(nullptr,429 Opts.getOption(options::OPT_fdx_rootsignature_version),430 A->getValue());431 A->claim();432 continue;433 }434 if (A->getOption().getID() == options::OPT_dxc_rootsig_define) {435 DAL->AddJoinedArg(nullptr,436 Opts.getOption(options::OPT_fdx_rootsignature_define),437 A->getValue());438 A->claim();439 continue;440 }441 if (A->getOption().getID() == options::OPT__SLASH_O) {442 StringRef OStr = A->getValue();443 if (OStr == "d") {444 DAL->AddFlagArg(nullptr, Opts.getOption(options::OPT_O0));445 A->claim();446 continue;447 } else {448 DAL->AddJoinedArg(nullptr, Opts.getOption(options::OPT_O), OStr);449 A->claim();450 continue;451 }452 }453 if (A->getOption().getID() == options::OPT_emit_pristine_llvm) {454 // Translate -fcgl into -emit-llvm and -disable-llvm-passes.455 DAL->AddFlagArg(nullptr, Opts.getOption(options::OPT_emit_llvm));456 DAL->AddFlagArg(nullptr,457 Opts.getOption(options::OPT_disable_llvm_passes));458 A->claim();459 continue;460 }461 if (A->getOption().getID() == options::OPT_dxc_hlsl_version) {462 // Translate -HV into -std for llvm463 // depending on the value given464 LangStandard::Kind LangStd = LangStandard::getHLSLLangKind(A->getValue());465 if (LangStd != LangStandard::lang_unspecified) {466 LangStandard l = LangStandard::getLangStandardForKind(LangStd);467 DAL->AddSeparateArg(nullptr, Opts.getOption(options::OPT_std_EQ),468 l.getName());469 } else {470 getDriver().Diag(diag::err_drv_invalid_value) << "HV" << A->getValue();471 }472 473 A->claim();474 continue;475 }476 if (A->getOption().getID() == options::OPT_dxc_gis) {477 // Translate -Gis into -ffp_model_EQ=strict478 DAL->AddSeparateArg(nullptr, Opts.getOption(options::OPT_ffp_model_EQ),479 "strict");480 A->claim();481 continue;482 }483 if (A->getOption().getID() == options::OPT_fvk_use_dx_layout) {484 // This is the only implemented layout so far.485 A->claim();486 continue;487 }488 489 if (A->getOption().getID() == options::OPT_fvk_use_scalar_layout) {490 getDriver().Diag(diag::err_drv_clang_unsupported) << A->getAsString(Args);491 A->claim();492 continue;493 }494 495 if (A->getOption().getID() == options::OPT_fvk_use_gl_layout) {496 getDriver().Diag(diag::err_drv_clang_unsupported) << A->getAsString(Args);497 A->claim();498 continue;499 }500 501 if (A->getOption().getID() == options::OPT_enable_16bit_types) {502 // Translate -enable-16bit-types into -fnative-half-type and503 // -fnative-int16-type504 DAL->AddFlagArg(nullptr, Opts.getOption(options::OPT_fnative_half_type));505 DAL->AddFlagArg(nullptr, Opts.getOption(options::OPT_fnative_int16_type));506 A->claim();507 continue;508 }509 510 DAL->append(A);511 }512 513 if (getArch() == llvm::Triple::spirv) {514 std::vector<std::string> SpvExtensionArgs =515 Args.getAllArgValues(options::OPT_fspv_extension_EQ);516 if (checkExtensionArgsAreValid(SpvExtensionArgs, getDriver())) {517 SmallString<1024> LlvmOption = getSpirvExtArg(SpvExtensionArgs);518 DAL->AddSeparateArg(nullptr, Opts.getOption(options::OPT_mllvm),519 LlvmOption);520 }521 Args.claimAllArgs(options::OPT_fspv_extension_EQ);522 }523 524 if (!DAL->hasArg(options::OPT_O_Group)) {525 DAL->AddJoinedArg(nullptr, Opts.getOption(options::OPT_O), "3");526 }527 528 return DAL;529}530 531bool HLSLToolChain::requiresValidation(DerivedArgList &Args) const {532 if (!Args.hasArg(options::OPT_dxc_Fo))533 return false;534 535 if (Args.getLastArg(options::OPT_dxc_disable_validation))536 return false;537 538 std::string DxvPath = GetProgramPath("dxv");539 if (DxvPath != "dxv")540 return true;541 542 getDriver().Diag(diag::warn_drv_dxc_missing_dxv);543 return false;544}545 546bool HLSLToolChain::requiresBinaryTranslation(DerivedArgList &Args) const {547 return Args.hasArg(options::OPT_metal) && Args.hasArg(options::OPT_dxc_Fo);548}549 550bool HLSLToolChain::requiresObjcopy(DerivedArgList &Args) const {551 return Args.hasArg(options::OPT_dxc_Fo) &&552 (Args.hasArg(options::OPT_dxc_strip_rootsignature) ||553 Args.hasArg(options::OPT_dxc_Frs) || isRootSignatureTarget(Args));554}555 556bool HLSLToolChain::isLastJob(DerivedArgList &Args,557 Action::ActionClass AC) const {558 // Note: we check in the reverse order of execution559 if (requiresBinaryTranslation(Args))560 return AC == Action::Action::BinaryTranslatorJobClass;561 if (requiresValidation(Args))562 return AC == Action::Action::BinaryAnalyzeJobClass;563 if (requiresObjcopy(Args))564 return AC == Action::Action::ObjcopyJobClass;565 566 // No translation, validation, or objcopy are required, so this action must567 // output to the result file.568 return true;569}570 571void HLSLToolChain::addClangWarningOptions(ArgStringList &CC1Args) const {572 CC1Args.push_back("-Wconversion");573}574