diff --git a/llvm/lib/Target/DirectX/CMakeLists.txt b/llvm/lib/Target/DirectX/CMakeLists.txt --- a/llvm/lib/Target/DirectX/CMakeLists.txt +++ b/llvm/lib/Target/DirectX/CMakeLists.txt @@ -4,6 +4,9 @@ tablegen(LLVM DirectXGenSubtargetInfo.inc -gen-subtarget) +set(LLVM_TARGET_DEFINITIONS DXIL.td) +tablegen(LLVM DXILOperation.inc -gen-dxil-operation) + add_public_tablegen_target(DirectXCommonTableGen) add_llvm_target(DirectXCodeGen diff --git a/llvm/lib/Target/DirectX/DXIL.td b/llvm/lib/Target/DirectX/DXIL.td new file mode 100644 --- /dev/null +++ b/llvm/lib/Target/DirectX/DXIL.td @@ -0,0 +1,107 @@ +//- DXIL.td - Describe DXIL operation -------------------------*- tablegen -*-// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This is a target description file for DXIL operation. +/// +//===----------------------------------------------------------------------===// + +include "llvm/IR/Intrinsics.td" + +class dxil_class { + string name = _name; +} +class dxil_category { + string name = _name; +} + +def Unary : dxil_class<"Unary">; +def Binary : dxil_class<"Binary">; + +def binary_uint : dxil_category<"Binary uint">; +def unary_float : dxil_category<"Unary float">; + + + +// "The parameter description for a DXIL instruction" +class dxil_param { + int pos = _pos; // position in parameter list + string llvm_type = type; // llvm type name, $o for overload, $r for resource + // type, $cb for legacy cbuffer, $u4 for u4 struct + string name = _name; // short, unique name + string doc = _doc; // the documentation description of this parameter + bit is_const = + _is_const; // whether this argument requires a constant value in the IR + string enum_name = _enum_name; // the name of the enum type if applicable + int max_value = + _max_value; // the maximum value for this parameter if applicable +} + +// "A representation for a DXIL instruction" +class dxil_inst { + string name = _name; // short, unique name + + string dxil_op = ""; // name of DXIL operation + int dxil_opid = 0; // ID of DXIL operation + dxil_class op_class; // name of the opcode class + dxil_category category; // classification for this instruction + string doc = ""; // the documentation description of this instruction + list ops = []; // the operands that this instruction takes + string oload_types = ""; // overload types if applicable + string fn_attr = ""; // attribute shorthands: rn=does not access + // memory,ro=only reads from memory, + bit is_deriv = 0; // whether this is some kind of derivative + bit is_gradient = 0; // whether this requires a gradient calculation + bit is_feedback = 0; // whether this is a sampler feedback op + bit is_wave = 0; // whether this requires in-wave, cross-lane functionality + bit requires_uniform_inputs = 0; // whether this operation requires that all + // of its inputs are uniform across the wave + // Group dxil operation for stats. + // Like how many atomic/float/uint/int/... instructions used in the program. + list stats_group = []; +} + +class dxil_op op_params, + list _stats_group = []> : dxil_inst { + let dxil_op = name; + let dxil_opid = code_id; + let doc = _doc; + let ops = op_params; + let op_class = code_class; + let category = op_category; + let oload_types = _oload_types; + let fn_attr = _fn_attr; + let stats_group = _stats_group; +} + +// The intrinsic which map directly to this dxil op. +class dxil_map_intrinsic { Intrinsic llvm_intrinsic = llvm_intrinsic_; } + +def Sin : dxil_op<"Sin", 13, Unary, unary_float, "returns sine(theta) for theta in radians.", + "half;float;", "rn", + [ + dxil_param<0, "$o", "", "operation result">, + dxil_param<1, "i32", "opcode", "DXIL opcode">, + dxil_param<2, "$o", "value", "input value"> + ], + ["floats"]>, + dxil_map_intrinsic; + +def UMax :dxil_op< "UMax", 39, Binary, binary_uint, "unsigned integer maximum. UMax(a,b) = a > b ? a : b", + "i16;i32;i64;", "rn", + [ + dxil_param<0, "$o", "", "operation result">, + dxil_param<1, "i32", "opcode", "DXIL opcode">, + dxil_param<2, "$o", "a", "input value">, + dxil_param<3, "$o", "b", "input value"> + ], + ["uints"]>, + dxil_map_intrinsic; \ No newline at end of file diff --git a/llvm/lib/Target/DirectX/DXILConstants.h b/llvm/lib/Target/DirectX/DXILConstants.h --- a/llvm/lib/Target/DirectX/DXILConstants.h +++ b/llvm/lib/Target/DirectX/DXILConstants.h @@ -14,14 +14,10 @@ namespace llvm { namespace DXIL { -// Enumeration for operations specified by DXIL -enum class OpCode : unsigned { - Sin = 13, // returns sine(theta) for theta in radians. -}; -// Groups for DXIL operations with equivalent function templates -enum class OpCodeClass : unsigned { - Unary, -}; + +#define DXIL_OP_ENUM +#include "DXILOperation.inc" +#undef DXIL_OP_ENUM } // namespace DXIL } // namespace llvm diff --git a/llvm/lib/Target/DirectX/DXILOpLowering.cpp b/llvm/lib/Target/DirectX/DXILOpLowering.cpp --- a/llvm/lib/Target/DirectX/DXILOpLowering.cpp +++ b/llvm/lib/Target/DirectX/DXILOpLowering.cpp @@ -65,7 +65,7 @@ case OverloadKind::ObjectType: case OverloadKind::UserDefineType: llvm_unreachable("invalid overload type for name"); - break; + return "void"; } } @@ -140,6 +140,7 @@ static const char *getOpCodeClassName(const OpCodeProperty &Prop) { // FIXME: generate this table with tableGen. static const char *OpCodeClassNames[] = { + "binary", "unary", }; unsigned Index = static_cast(Prop.OpCodeClass); @@ -163,6 +164,9 @@ static const OpCodeProperty OpCodeProps[] = { {DXIL::OpCode::Sin, "Sin", OpCodeClass::Unary, OverloadKind::FLOAT | OverloadKind::HALF, Attribute::AttrKind::ReadNone}, + {DXIL::OpCode::UMax, "UMax", OpCodeClass::Binary, + OverloadKind::I16 | OverloadKind::I32 | OverloadKind::I64, + Attribute::AttrKind::ReadNone}, }; // FIXME: change search to indexing with // DXILOp once all DXIL op is added. @@ -230,7 +234,8 @@ static bool lowerIntrinsics(Module &M) { bool Updated = false; static SmallDenseMap LowerMap = { - {Intrinsic::sin, DXIL::OpCode::Sin}}; + {Intrinsic::sin, DXIL::OpCode::Sin}, + {Intrinsic::umax, DXIL::OpCode::UMax}}; for (Function &F : make_early_inc_range(M.functions())) { if (!F.isDeclaration()) continue; diff --git a/llvm/test/CodeGen/DirectX/umax.ll b/llvm/test/CodeGen/DirectX/umax.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/DirectX/umax.ll @@ -0,0 +1,34 @@ +; RUN: opt -S -dxil-op-lower < %s | FileCheck %s + +; Make sure dxil operation function calls for umax are generated for i32/i64. +; CHECK:call i32 @dx.op.binary.i32(i32 39, i32 %{{.*}}, i32 %{{.*}}) +; CHECK:call i64 @dx.op.binary.i64(i32 39, i64 %{{.*}}, i64 %{{.*}}) + +target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64" +target triple = "dxil-pc-shadermodel6.7-library" + +; Function Attrs: noinline nounwind optnone +define noundef i32 @_Z3foof(i32 noundef %a, i32 noundef %b) #0 { +entry: + %0 = call i32 @llvm.umax.i32(i32 %a, i32 %b) + ret i32 %0 +} + +define noundef i64 @bar(i64 noundef %a, i64 noundef %b) #0 { +entry: + %0 = call i64 @llvm.umax.i64(i64 %a, i64 %b) + ret i64 %0 +} + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.umax.i32(i32, i32) #1 +declare i64 @llvm.umax.i64(i64, i64) #1 + +attributes #0 = { noinline nounwind optnone "frame-pointer"="none" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind readnone speculatable willreturn } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 15.0.0 (https://github.com/llvm/llvm-project.git 73417c517644db5c419c85c0b3cb6750172fcab5)"} diff --git a/llvm/utils/TableGen/CMakeLists.txt b/llvm/utils/TableGen/CMakeLists.txt --- a/llvm/utils/TableGen/CMakeLists.txt +++ b/llvm/utils/TableGen/CMakeLists.txt @@ -26,6 +26,7 @@ DFAPacketizerEmitter.cpp DirectiveEmitter.cpp DisassemblerEmitter.cpp + DXILEmitter.cpp ExegesisEmitter.cpp FastISelEmitter.cpp GICombinerEmitter.cpp diff --git a/llvm/utils/TableGen/DXILEmitter.cpp b/llvm/utils/TableGen/DXILEmitter.cpp new file mode 100644 --- /dev/null +++ b/llvm/utils/TableGen/DXILEmitter.cpp @@ -0,0 +1,202 @@ +//===- DXILEmitter.cpp - DXIL operation Emitter ---------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// DXILEmitter uses the descriptions of DXIL operation to construct enum and +// helper functions for DXIL operation. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/MapVector.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/TableGen/Error.h" +#include "llvm/TableGen/Record.h" + +using namespace llvm; + +namespace { + +struct DXILShaderModel { + int Major; + int Minor; +}; +struct DXILParam { + int Pos; // position in parameter list + StringRef Type; // llvm type name, $o for overload, $r for resource + // type, $cb for legacy cbuffer, $u4 for u4 struct + StringRef Name; // short, unique name + StringRef Doc; // the documentation description of this parameter + bool IsConst; // whether this argument requires a constant value in the IR + StringRef EnumName; // the name of the enum type if applicable + int MaxValue; // the maximum value for this parameter if applicable + DXILParam(const Record *R) { + Name = R->getValueAsString("name"); + Pos = R->getValueAsInt("pos"); + Type = R->getValueAsString("llvm_type"); + if (R->getValue("doc")) + Doc = R->getValueAsString("doc"); + IsConst = R->getValueAsBit("is_const"); + EnumName = R->getValueAsString("enum_name"); + MaxValue = R->getValueAsInt("max_value"); + } +}; + +struct DXILOperationData { + StringRef Name; // short, unique name + + StringRef DXILOp; // name of DXIL operation + int DXILOpID; // ID of DXIL operation + StringRef DXILClass; // name of the opcode class + StringRef Category; // classification for this instruction + StringRef Doc; // the documentation description of this instruction + + SmallVector Params; // the operands that this instruction takes + StringRef OverloadTypes; // overload types if applicable + StringRef FnAttr; // attribute shorthands: rn=does not access + // memory,ro=only reads from memory, + bool IsDeriv; // whether this is some kind of derivative + bool IsGradient; // whether this requires a gradient calculation + bool IsFeedback; // whether this is a sampler feedback op + bool IsWave; // whether this requires in-wave, cross-lane functionality + bool RequiresUniformInputs; // whether this operation requires that all + // of its inputs are uniform across the wave + SmallVector + ShaderStages; // shader stages to which this applies, empty for all. + DXILShaderModel ShaderModel; // minimum shader model required + DXILShaderModel ShaderModelTranslated; // minimum shader model required with + // translation by linker + SmallVector counters; // counters for this inst. + DXILOperationData(const Record *R) { + Name = R->getValueAsString("name"); + DXILOp = R->getValueAsString("dxil_op"); + DXILOpID = R->getValueAsInt("dxil_opid"); + DXILClass = R->getValueAsDef("op_class")->getValueAsString("name"); + Category = R->getValueAsDef("category")->getValueAsString("name"); + + Doc = R->getValueAsString("doc"); + ListInit *ParamList = R->getValueAsListInit("ops"); + for (unsigned i = 0; i < ParamList->size(); ++i) { + Record *Param = ParamList->getElementAsRecord(i); + Params.emplace_back(DXILParam(Param)); + } + OverloadTypes = R->getValueAsString("oload_types"); + FnAttr = R->getValueAsString("fn_attr"); + } +}; +} // end anonymous namespace + +static void emitDXILOpEnum(DXILOperationData &DXILOp, raw_ostream &OS) { + // Name = ID, // Doc + OS << DXILOp.Name << " = " << DXILOp.DXILOpID << ", // " << DXILOp.Doc + << "\n"; +} + +static std::string buildCategoryStr(StringSet<> &Cetegorys) { + std::string Str; + raw_string_ostream OS(Str); + for (auto &It : Cetegorys) { + OS << " " << It.getKey(); + } + return OS.str(); +} + +// Emit enum declaration for DXIL. +static void emitDXILEnums(std::vector &DXILOps, + raw_ostream &OS) { + // Sort by Category + OpName. + std::sort(DXILOps.begin(), DXILOps.end(), + [](DXILOperationData &A, DXILOperationData &B) { + // Group by Category first. + if (A.Category == B.Category) + // Inside same Category, order by OpName. + return A.DXILOp < B.DXILOp; + else + return A.Category < B.Category; + }); + + OS << "// Enumeration for operations specified by DXIL\n"; + OS << "enum class OpCode : unsigned {\n"; + + StringMap> ClassMap; + StringRef PrevCategory = ""; + for (auto &DXILOp : DXILOps) { + StringRef Category = DXILOp.Category; + if (Category != PrevCategory) { + OS << "\n// " << Category << "\n"; + PrevCategory = Category; + } + emitDXILOpEnum(DXILOp, OS); + auto It = ClassMap.find(DXILOp.DXILClass); + if (It != ClassMap.end()) { + It->second.insert(DXILOp.Category); + } else { + ClassMap[DXILOp.DXILClass].insert(DXILOp.Category); + } + } + + OS << "\n};\n\n"; + + std::vector> ClassVec; + for (auto &It : ClassMap) { + ClassVec.emplace_back( + std::make_pair(It.getKey().str(), buildCategoryStr(It.second))); + } + // Sort by Category + ClassName. + std::sort(ClassVec.begin(), ClassVec.end(), + [](std::pair &A, + std::pair &B) { + StringRef ClassA = A.first; + StringRef CategoryA = A.second; + StringRef ClassB = B.first; + StringRef CategoryB = B.second; + // Group by Category first. + if (CategoryA == CategoryB) + // Inside same Category, order by ClassName. + return ClassA < ClassB; + else + return CategoryA < CategoryB; + }); + + OS << "// Groups for DXIL operations with equivalent function templates\n"; + OS << "enum class OpCodeClass : unsigned {\n"; + PrevCategory = ""; + for (auto &It : ClassVec) { + + StringRef Category = It.second; + if (Category != PrevCategory) { + OS << "\n// " << Category << "\n"; + PrevCategory = Category; + } + StringRef Name = It.first; + OS << Name << ",\n"; + } + OS << "\n};\n\n"; +} + +namespace llvm { + +void EmitDXILOperation(RecordKeeper &Records, raw_ostream &OS) { + std::vector Ops = Records.getAllDerivedDefinitions("dxil_op"); + OS << "// Generated code, do not edit.\n"; + OS << "\n"; + + std::vector DXILOps; + DXILOps.reserve(Ops.size()); + for (auto *Record : Ops) { + DXILOps.emplace_back(DXILOperationData(Record)); + } + + OS << "#ifdef DXIL_OP_ENUM\n"; + emitDXILEnums(DXILOps, OS); + OS << "#endif\n\n"; + + OS << "\n"; +} + +} // namespace llvm diff --git a/llvm/utils/TableGen/TableGen.cpp b/llvm/utils/TableGen/TableGen.cpp --- a/llvm/utils/TableGen/TableGen.cpp +++ b/llvm/utils/TableGen/TableGen.cpp @@ -57,6 +57,7 @@ GenAutomata, GenDirectivesEnumDecl, GenDirectivesEnumImpl, + GenDXILOperation, }; namespace llvm { @@ -138,7 +139,9 @@ clEnumValN(GenDirectivesEnumDecl, "gen-directive-decl", "Generate directive related declaration code (header file)"), clEnumValN(GenDirectivesEnumImpl, "gen-directive-impl", - "Generate directive related implementation code"))); + "Generate directive related implementation code"), + clEnumValN(GenDXILOperation, "gen-dxil-operation", + "Generate DXIL operation information"))); cl::OptionCategory PrintEnumsCat("Options for -print-enums"); cl::opt Class("class", cl::desc("Print Enum list for this class"), @@ -272,6 +275,9 @@ case GenDirectivesEnumImpl: EmitDirectivesImpl(Records, OS); break; + case GenDXILOperation: + EmitDXILOperation(Records, OS); + break; } return false; diff --git a/llvm/utils/TableGen/TableGenBackends.h b/llvm/utils/TableGen/TableGenBackends.h --- a/llvm/utils/TableGen/TableGenBackends.h +++ b/llvm/utils/TableGen/TableGenBackends.h @@ -93,6 +93,7 @@ void EmitAutomata(RecordKeeper &RK, raw_ostream &OS); void EmitDirectivesDecl(RecordKeeper &RK, raw_ostream &OS); void EmitDirectivesImpl(RecordKeeper &RK, raw_ostream &OS); +void EmitDXILOperation(RecordKeeper &RK, raw_ostream &OS); } // End llvm namespace