622 lines · cpp
1//===- DXILOpBuilder.cpp - Helper class for build DIXLOp functions --------===//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/// \file This file contains class to help build DXIL op functions.10//===----------------------------------------------------------------------===//11 12#include "DXILOpBuilder.h"13#include "DXILConstants.h"14#include "llvm/IR/Module.h"15#include "llvm/Support/DXILABI.h"16#include "llvm/Support/ErrorHandling.h"17#include <optional>18 19using namespace llvm;20using namespace llvm::dxil;21 22constexpr StringLiteral DXILOpNamePrefix = "dx.op.";23 24namespace {25enum OverloadKind : uint16_t {26 UNDEFINED = 0,27 VOID = 1,28 HALF = 1 << 1,29 FLOAT = 1 << 2,30 DOUBLE = 1 << 3,31 I1 = 1 << 4,32 I8 = 1 << 5,33 I16 = 1 << 6,34 I32 = 1 << 7,35 I64 = 1 << 8,36 UserDefineType = 1 << 9,37 ObjectType = 1 << 10,38};39struct Version {40 unsigned Major = 0;41 unsigned Minor = 0;42};43 44struct OpOverload {45 Version DXILVersion;46 uint16_t ValidTys;47};48} // namespace49 50struct OpStage {51 Version DXILVersion;52 uint32_t ValidStages;53};54 55static const char *getOverloadTypeName(OverloadKind Kind) {56 switch (Kind) {57 case OverloadKind::HALF:58 return "f16";59 case OverloadKind::FLOAT:60 return "f32";61 case OverloadKind::DOUBLE:62 return "f64";63 case OverloadKind::I1:64 return "i1";65 case OverloadKind::I8:66 return "i8";67 case OverloadKind::I16:68 return "i16";69 case OverloadKind::I32:70 return "i32";71 case OverloadKind::I64:72 return "i64";73 case OverloadKind::VOID:74 case OverloadKind::UNDEFINED:75 return "void";76 case OverloadKind::ObjectType:77 case OverloadKind::UserDefineType:78 break;79 }80 llvm_unreachable("invalid overload type for name");81}82 83static OverloadKind getOverloadKind(Type *Ty) {84 if (!Ty)85 return OverloadKind::VOID;86 87 Type::TypeID T = Ty->getTypeID();88 switch (T) {89 case Type::VoidTyID:90 return OverloadKind::VOID;91 case Type::HalfTyID:92 return OverloadKind::HALF;93 case Type::FloatTyID:94 return OverloadKind::FLOAT;95 case Type::DoubleTyID:96 return OverloadKind::DOUBLE;97 case Type::IntegerTyID: {98 IntegerType *ITy = cast<IntegerType>(Ty);99 unsigned Bits = ITy->getBitWidth();100 switch (Bits) {101 case 1:102 return OverloadKind::I1;103 case 8:104 return OverloadKind::I8;105 case 16:106 return OverloadKind::I16;107 case 32:108 return OverloadKind::I32;109 case 64:110 return OverloadKind::I64;111 default:112 llvm_unreachable("invalid overload type");113 return OverloadKind::VOID;114 }115 }116 case Type::PointerTyID:117 return OverloadKind::UserDefineType;118 case Type::StructTyID: {119 // TODO: This is a hack. As described in DXILEmitter.cpp, we need to rework120 // how we're handling overloads and remove the `OverloadKind` proxy enum.121 StructType *ST = cast<StructType>(Ty);122 return getOverloadKind(ST->getElementType(0));123 }124 default:125 return OverloadKind::UNDEFINED;126 }127}128 129static std::string getTypeName(OverloadKind Kind, Type *Ty) {130 if (Kind < OverloadKind::UserDefineType) {131 return getOverloadTypeName(Kind);132 } else if (Kind == OverloadKind::UserDefineType) {133 StructType *ST = cast<StructType>(Ty);134 return ST->getStructName().str();135 } else if (Kind == OverloadKind::ObjectType) {136 StructType *ST = cast<StructType>(Ty);137 return ST->getStructName().str();138 } else {139 std::string Str;140 raw_string_ostream OS(Str);141 Ty->print(OS);142 return OS.str();143 }144}145 146// Static properties.147struct OpCodeProperty {148 dxil::OpCode OpCode;149 // Offset in DXILOpCodeNameTable.150 unsigned OpCodeNameOffset;151 dxil::OpCodeClass OpCodeClass;152 // Offset in DXILOpCodeClassNameTable.153 unsigned OpCodeClassNameOffset;154 llvm::SmallVector<OpOverload> Overloads;155 llvm::SmallVector<OpStage> Stages;156 int OverloadParamIndex; // parameter index which control the overload.157 // When < 0, should be only 1 overload type.158};159 160// Include getOpCodeClassName getOpCodeProperty, getOpCodeName and161// getOpCodeParameterKind which generated by tableGen.162#define DXIL_OP_OPERATION_TABLE163#include "DXILOperation.inc"164#undef DXIL_OP_OPERATION_TABLE165 166static std::string constructOverloadName(OverloadKind Kind, Type *Ty,167 const OpCodeProperty &Prop) {168 if (Kind == OverloadKind::VOID) {169 return (Twine(DXILOpNamePrefix) + getOpCodeClassName(Prop)).str();170 }171 return (Twine(DXILOpNamePrefix) + getOpCodeClassName(Prop) + "." +172 getTypeName(Kind, Ty))173 .str();174}175 176static std::string constructOverloadTypeName(OverloadKind Kind,177 StringRef TypeName) {178 if (Kind == OverloadKind::VOID)179 return TypeName.str();180 181 assert(Kind < OverloadKind::UserDefineType && "invalid overload kind");182 return (Twine(TypeName) + getOverloadTypeName(Kind)).str();183}184 185static StructType *getOrCreateStructType(StringRef Name,186 ArrayRef<Type *> EltTys,187 LLVMContext &Ctx) {188 StructType *ST = StructType::getTypeByName(Ctx, Name);189 if (ST)190 return ST;191 192 return StructType::create(Ctx, EltTys, Name);193}194 195static StructType *getResRetType(Type *ElementTy) {196 LLVMContext &Ctx = ElementTy->getContext();197 OverloadKind Kind = getOverloadKind(ElementTy);198 std::string TypeName = constructOverloadTypeName(Kind, "dx.types.ResRet.");199 Type *FieldTypes[5] = {ElementTy, ElementTy, ElementTy, ElementTy,200 Type::getInt32Ty(Ctx)};201 return getOrCreateStructType(TypeName, FieldTypes, Ctx);202}203 204static StructType *getCBufRetType(Type *ElementTy) {205 LLVMContext &Ctx = ElementTy->getContext();206 OverloadKind Kind = getOverloadKind(ElementTy);207 std::string TypeName = constructOverloadTypeName(Kind, "dx.types.CBufRet.");208 209 // 64-bit types only have two elements210 if (ElementTy->isDoubleTy() || ElementTy->isIntegerTy(64))211 return getOrCreateStructType(TypeName, {ElementTy, ElementTy}, Ctx);212 213 // 16-bit types pack 8 elements and have .8 in their name to differentiate214 // from min-precision types.215 if (ElementTy->isHalfTy() || ElementTy->isIntegerTy(16)) {216 TypeName += ".8";217 return getOrCreateStructType(TypeName,218 {ElementTy, ElementTy, ElementTy, ElementTy,219 ElementTy, ElementTy, ElementTy, ElementTy},220 Ctx);221 }222 223 return getOrCreateStructType(224 TypeName, {ElementTy, ElementTy, ElementTy, ElementTy}, Ctx);225}226 227static StructType *getHandleType(LLVMContext &Ctx) {228 return getOrCreateStructType("dx.types.Handle", PointerType::getUnqual(Ctx),229 Ctx);230}231 232static StructType *getResBindType(LLVMContext &Context) {233 if (auto *ST = StructType::getTypeByName(Context, "dx.types.ResBind"))234 return ST;235 Type *Int32Ty = Type::getInt32Ty(Context);236 Type *Int8Ty = Type::getInt8Ty(Context);237 return StructType::create({Int32Ty, Int32Ty, Int32Ty, Int8Ty},238 "dx.types.ResBind");239}240 241static StructType *getResPropsType(LLVMContext &Context) {242 if (auto *ST =243 StructType::getTypeByName(Context, "dx.types.ResourceProperties"))244 return ST;245 Type *Int32Ty = Type::getInt32Ty(Context);246 return StructType::create({Int32Ty, Int32Ty}, "dx.types.ResourceProperties");247}248 249static StructType *getSplitDoubleType(LLVMContext &Context) {250 if (auto *ST = StructType::getTypeByName(Context, "dx.types.splitdouble"))251 return ST;252 Type *Int32Ty = Type::getInt32Ty(Context);253 return StructType::create({Int32Ty, Int32Ty}, "dx.types.splitdouble");254}255 256static StructType *getBinaryWithCarryType(LLVMContext &Context) {257 if (auto *ST = StructType::getTypeByName(Context, "dx.types.i32c"))258 return ST;259 Type *Int32Ty = Type::getInt32Ty(Context);260 Type *Int1Ty = Type::getInt1Ty(Context);261 return StructType::create({Int32Ty, Int1Ty}, "dx.types.i32c");262}263 264static StructType *getDimensionsType(LLVMContext &Ctx) {265 Type *Int32Ty = Type::getInt32Ty(Ctx);266 return getOrCreateStructType("dx.types.Dimensions",267 {Int32Ty, Int32Ty, Int32Ty, Int32Ty}, Ctx);268}269 270static Type *getTypeFromOpParamType(OpParamType Kind, LLVMContext &Ctx,271 Type *OverloadTy) {272 switch (Kind) {273 case OpParamType::VoidTy:274 return Type::getVoidTy(Ctx);275 case OpParamType::HalfTy:276 return Type::getHalfTy(Ctx);277 case OpParamType::FloatTy:278 return Type::getFloatTy(Ctx);279 case OpParamType::DoubleTy:280 return Type::getDoubleTy(Ctx);281 case OpParamType::Int1Ty:282 return Type::getInt1Ty(Ctx);283 case OpParamType::Int8Ty:284 return Type::getInt8Ty(Ctx);285 case OpParamType::Int16Ty:286 return Type::getInt16Ty(Ctx);287 case OpParamType::Int32Ty:288 return Type::getInt32Ty(Ctx);289 case OpParamType::Int64Ty:290 return Type::getInt64Ty(Ctx);291 case OpParamType::OverloadTy:292 return OverloadTy;293 case OpParamType::ResRetHalfTy:294 return getResRetType(Type::getHalfTy(Ctx));295 case OpParamType::ResRetFloatTy:296 return getResRetType(Type::getFloatTy(Ctx));297 case OpParamType::ResRetDoubleTy:298 return getResRetType(Type::getDoubleTy(Ctx));299 case OpParamType::ResRetInt16Ty:300 return getResRetType(Type::getInt16Ty(Ctx));301 case OpParamType::ResRetInt32Ty:302 return getResRetType(Type::getInt32Ty(Ctx));303 case OpParamType::ResRetInt64Ty:304 return getResRetType(Type::getInt64Ty(Ctx));305 case OpParamType::CBufRetHalfTy:306 return getCBufRetType(Type::getHalfTy(Ctx));307 case OpParamType::CBufRetFloatTy:308 return getCBufRetType(Type::getFloatTy(Ctx));309 case OpParamType::CBufRetDoubleTy:310 return getCBufRetType(Type::getDoubleTy(Ctx));311 case OpParamType::CBufRetInt16Ty:312 return getCBufRetType(Type::getInt16Ty(Ctx));313 case OpParamType::CBufRetInt32Ty:314 return getCBufRetType(Type::getInt32Ty(Ctx));315 case OpParamType::CBufRetInt64Ty:316 return getCBufRetType(Type::getInt64Ty(Ctx));317 case OpParamType::HandleTy:318 return getHandleType(Ctx);319 case OpParamType::ResBindTy:320 return getResBindType(Ctx);321 case OpParamType::ResPropsTy:322 return getResPropsType(Ctx);323 case OpParamType::SplitDoubleTy:324 return getSplitDoubleType(Ctx);325 case OpParamType::BinaryWithCarryTy:326 return getBinaryWithCarryType(Ctx);327 case OpParamType::DimensionsTy:328 return getDimensionsType(Ctx);329 }330 llvm_unreachable("Invalid parameter kind");331 return nullptr;332}333 334static ShaderKind getShaderKindEnum(Triple::EnvironmentType EnvType) {335 switch (EnvType) {336 case Triple::Pixel:337 return ShaderKind::pixel;338 case Triple::Vertex:339 return ShaderKind::vertex;340 case Triple::Geometry:341 return ShaderKind::geometry;342 case Triple::Hull:343 return ShaderKind::hull;344 case Triple::Domain:345 return ShaderKind::domain;346 case Triple::Compute:347 return ShaderKind::compute;348 case Triple::Library:349 return ShaderKind::library;350 case Triple::RayGeneration:351 return ShaderKind::raygeneration;352 case Triple::Intersection:353 return ShaderKind::intersection;354 case Triple::AnyHit:355 return ShaderKind::anyhit;356 case Triple::ClosestHit:357 return ShaderKind::closesthit;358 case Triple::Miss:359 return ShaderKind::miss;360 case Triple::Callable:361 return ShaderKind::callable;362 case Triple::Mesh:363 return ShaderKind::mesh;364 case Triple::Amplification:365 return ShaderKind::amplification;366 default:367 break;368 }369 llvm_unreachable(370 "Shader Kind Not Found - Invalid DXIL Environment Specified");371}372 373static SmallVector<Type *>374getArgTypesFromOpParamTypes(ArrayRef<dxil::OpParamType> Types,375 LLVMContext &Context, Type *OverloadTy) {376 SmallVector<Type *> ArgTys;377 ArgTys.emplace_back(Type::getInt32Ty(Context));378 for (dxil::OpParamType Ty : Types)379 ArgTys.emplace_back(getTypeFromOpParamType(Ty, Context, OverloadTy));380 return ArgTys;381}382 383/// Construct DXIL function type. This is the type of a function with384/// the following prototype385/// OverloadType dx.op.<opclass>.<return-type>(int opcode, <param types>)386/// <param-types> are constructed from types in Prop.387static FunctionType *getDXILOpFunctionType(dxil::OpCode OpCode,388 LLVMContext &Context,389 Type *OverloadTy) {390 391 switch (OpCode) {392#define DXIL_OP_FUNCTION_TYPE(OpCode, RetType, ...) \393 case OpCode: \394 return FunctionType::get( \395 getTypeFromOpParamType(RetType, Context, OverloadTy), \396 getArgTypesFromOpParamTypes({__VA_ARGS__}, Context, OverloadTy), \397 /*isVarArg=*/false);398#include "DXILOperation.inc"399 }400 llvm_unreachable("Invalid OpCode?");401}402 403/// Get index of the property from PropList valid for the most recent404/// DXIL version not greater than DXILVer.405/// PropList is expected to be sorted in ascending order of DXIL version.406template <typename T>407static std::optional<size_t> getPropIndex(ArrayRef<T> PropList,408 const VersionTuple DXILVer) {409 size_t Index = PropList.size() - 1;410 for (auto Iter = PropList.rbegin(); Iter != PropList.rend();411 Iter++, Index--) {412 const T &Prop = *Iter;413 if (VersionTuple(Prop.DXILVersion.Major, Prop.DXILVersion.Minor) <=414 DXILVer) {415 return Index;416 }417 }418 return std::nullopt;419}420 421// Helper function to pack an OpCode and VersionTuple into a uint64_t for use422// in a switch statement423constexpr static uint64_t computeSwitchEnum(dxil::OpCode OpCode,424 uint16_t VersionMajor,425 uint16_t VersionMinor) {426 uint64_t OpCodePack = (uint64_t)OpCode;427 return (OpCodePack << 32) | (VersionMajor << 16) | VersionMinor;428}429 430// Retreive all the set attributes for a DXIL OpCode given the targeted431// DXILVersion432static dxil::Attributes getDXILAttributes(dxil::OpCode OpCode,433 VersionTuple DXILVersion) {434 // Instantiate all versions to iterate through435 SmallVector<Version> Versions = {436#define DXIL_VERSION(MAJOR, MINOR) {MAJOR, MINOR},437#include "DXILOperation.inc"438 };439 440 dxil::Attributes Attributes;441 for (auto Version : Versions) {442 if (DXILVersion < VersionTuple(Version.Major, Version.Minor))443 continue;444 445 // Switch through and match an OpCode with the specific version and set the446 // corresponding flag(s) if available447 switch (computeSwitchEnum(OpCode, Version.Major, Version.Minor)) {448#define DXIL_OP_ATTRIBUTES(OpCode, VersionMajor, VersionMinor, ...) \449 case computeSwitchEnum(OpCode, VersionMajor, VersionMinor): { \450 auto Other = dxil::Attributes{__VA_ARGS__}; \451 Attributes |= Other; \452 break; \453 };454#include "DXILOperation.inc"455 }456 }457 return Attributes;458}459 460// Retreive the set of DXIL Attributes given the version and map them to an461// llvm function attribute that is set onto the instruction462static void setDXILAttributes(CallInst *CI, dxil::OpCode OpCode,463 VersionTuple DXILVersion) {464 dxil::Attributes Attributes = getDXILAttributes(OpCode, DXILVersion);465 if (Attributes.ReadNone)466 CI->setDoesNotAccessMemory();467 if (Attributes.ReadOnly)468 CI->setOnlyReadsMemory();469 if (Attributes.NoReturn)470 CI->setDoesNotReturn();471 if (Attributes.NoDuplicate)472 CI->setCannotDuplicate();473 return;474}475 476namespace llvm {477namespace dxil {478 479// No extra checks on TargetTriple need be performed to verify that the480// Triple is well-formed or that the target is supported since these checks481// would have been done at the time the module M is constructed in the earlier482// stages of compilation.483DXILOpBuilder::DXILOpBuilder(Module &M) : M(M), IRB(M.getContext()) {484 const Triple &TT = M.getTargetTriple();485 DXILVersion = TT.getDXILVersion();486 ShaderStage = TT.getEnvironment();487 // Ensure Environment type is known488 if (ShaderStage == Triple::UnknownEnvironment) {489 reportFatalUsageError(490 Twine(DXILVersion.getAsString()) +491 ": Unknown Compilation Target Shader Stage specified ");492 }493}494 495static Error makeOpError(dxil::OpCode OpCode, Twine Msg) {496 return make_error<StringError>(497 Twine("Cannot create ") + getOpCodeName(OpCode) + " operation: " + Msg,498 inconvertibleErrorCode());499}500 501Expected<CallInst *> DXILOpBuilder::tryCreateOp(dxil::OpCode OpCode,502 ArrayRef<Value *> Args,503 const Twine &Name,504 Type *RetTy) {505 const OpCodeProperty *Prop = getOpCodeProperty(OpCode);506 507 Type *OverloadTy = nullptr;508 if (Prop->OverloadParamIndex == 0) {509 if (!RetTy)510 return makeOpError(OpCode, "Op overloaded on unknown return type");511 OverloadTy = RetTy;512 } else if (Prop->OverloadParamIndex > 0) {513 // The index counts including the return type514 unsigned ArgIndex = Prop->OverloadParamIndex - 1;515 if (static_cast<unsigned>(ArgIndex) >= Args.size())516 return makeOpError(OpCode, "Wrong number of arguments");517 OverloadTy = Args[ArgIndex]->getType();518 }519 520 FunctionType *DXILOpFT =521 getDXILOpFunctionType(OpCode, M.getContext(), OverloadTy);522 523 std::optional<size_t> OlIndexOrErr =524 getPropIndex(ArrayRef(Prop->Overloads), DXILVersion);525 if (!OlIndexOrErr.has_value())526 return makeOpError(OpCode, Twine("No valid overloads for DXIL version ") +527 DXILVersion.getAsString());528 529 uint16_t ValidTyMask = Prop->Overloads[*OlIndexOrErr].ValidTys;530 531 OverloadKind Kind = getOverloadKind(OverloadTy);532 533 // Check if the operation supports overload types and OverloadTy is valid534 // per the specified types for the operation535 if ((ValidTyMask != OverloadKind::UNDEFINED) &&536 (ValidTyMask & (uint16_t)Kind) == 0)537 return makeOpError(OpCode, "Invalid overload type");538 539 // Perform necessary checks to ensure Opcode is valid in the targeted shader540 // kind541 std::optional<size_t> StIndexOrErr =542 getPropIndex(ArrayRef(Prop->Stages), DXILVersion);543 if (!StIndexOrErr.has_value())544 return makeOpError(OpCode, Twine("No valid stage for DXIL version ") +545 DXILVersion.getAsString());546 547 uint16_t ValidShaderKindMask = Prop->Stages[*StIndexOrErr].ValidStages;548 549 // Ensure valid shader stage properties are specified550 if (ValidShaderKindMask == ShaderKind::removed)551 return makeOpError(OpCode, "Operation has been removed");552 553 // Shader stage need not be validated since getShaderKindEnum() fails554 // for unknown shader stage.555 556 // Verify the target shader stage is valid for the DXIL operation557 ShaderKind ModuleStagekind = getShaderKindEnum(ShaderStage);558 if (!(ValidShaderKindMask & ModuleStagekind))559 return makeOpError(OpCode, "Invalid stage");560 561 std::string DXILFnName = constructOverloadName(Kind, OverloadTy, *Prop);562 FunctionCallee DXILFn = M.getOrInsertFunction(DXILFnName, DXILOpFT);563 564 // We need to inject the opcode as the first argument.565 SmallVector<Value *> OpArgs;566 OpArgs.push_back(IRB.getInt32(llvm::to_underlying(OpCode)));567 OpArgs.append(Args.begin(), Args.end());568 569 // Create the function call instruction570 CallInst *CI = IRB.CreateCall(DXILFn, OpArgs, Name);571 572 // We then need to attach available function attributes573 setDXILAttributes(CI, OpCode, DXILVersion);574 575 return CI;576}577 578CallInst *DXILOpBuilder::createOp(dxil::OpCode OpCode, ArrayRef<Value *> Args,579 const Twine &Name, Type *RetTy) {580 Expected<CallInst *> Result = tryCreateOp(OpCode, Args, Name, RetTy);581 if (Error E = Result.takeError())582 llvm_unreachable("Invalid arguments for operation");583 return *Result;584}585 586StructType *DXILOpBuilder::getResRetType(Type *ElementTy) {587 return ::getResRetType(ElementTy);588}589 590StructType *DXILOpBuilder::getCBufRetType(Type *ElementTy) {591 return ::getCBufRetType(ElementTy);592}593 594StructType *DXILOpBuilder::getHandleType() {595 return ::getHandleType(IRB.getContext());596}597 598Constant *DXILOpBuilder::getResBind(uint32_t LowerBound, uint32_t UpperBound,599 uint32_t SpaceID, dxil::ResourceClass RC) {600 Type *Int32Ty = IRB.getInt32Ty();601 Type *Int8Ty = IRB.getInt8Ty();602 return ConstantStruct::get(603 getResBindType(IRB.getContext()),604 {ConstantInt::get(Int32Ty, LowerBound),605 ConstantInt::get(Int32Ty, UpperBound),606 ConstantInt::get(Int32Ty, SpaceID),607 ConstantInt::get(Int8Ty, llvm::to_underlying(RC))});608}609 610Constant *DXILOpBuilder::getResProps(uint32_t Word0, uint32_t Word1) {611 Type *Int32Ty = IRB.getInt32Ty();612 return ConstantStruct::get(613 getResPropsType(IRB.getContext()),614 {ConstantInt::get(Int32Ty, Word0), ConstantInt::get(Int32Ty, Word1)});615}616 617const char *DXILOpBuilder::getOpCodeName(dxil::OpCode DXILOp) {618 return ::getOpCodeName(DXILOp);619}620} // namespace dxil621} // namespace llvm622