diff --git a/CMakeLists.txt b/CMakeLists.txt index 83584dcae8..97bed18c12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -311,8 +311,8 @@ configure_file(driver/ldc_version.d.in driver/ldc_version.d) file(GLOB_RECURSE FE_SRC_D dmd/*.d) file(GLOB_RECURSE FE_HDR dmd/*.h) file(GLOB_RECURSE FE_RES dmd/res/*.*) -file(GLOB_RECURSE GEN_SRC gen/*.cpp gen/abi/*.cpp) -file(GLOB_RECURSE GEN_HDR gen/*.h gen/abi/*.h) +file(GLOB_RECURSE GEN_SRC gen/*.cpp gen/abi/*.cpp gen/dcompute/*.cpp) +file(GLOB_RECURSE GEN_HDR gen/*.h gen/abi/*.h gen/dcompute/*.h) file(GLOB_RECURSE GEN_SRC_D gen/*.d) file(GLOB_RECURSE IR_SRC ir/*.cpp) file(GLOB_RECURSE IR_HDR ir/*.h) diff --git a/driver/cl_options.cpp b/driver/cl_options.cpp index c101cf199c..a6ee58e92c 100644 --- a/driver/cl_options.cpp +++ b/driver/cl_options.cpp @@ -756,7 +756,7 @@ cl::list dcomputeTargets("mdcompute-targets", cl::CommaSeparated, cl::desc("Generates code for the specified DCompute target" " list. Use 'ocl-xy0' for OpenCL x.y, and " - "'cuda-xy0' for CUDA CC x.y"), + "'cuda-xy0' for CUDA CC x.y, and 'vulkan-xy0' for Vulkan x.y"), cl::value_desc("targets")); cl::opt dcomputeFilePrefix("mdcompute-file-prefix", diff --git a/driver/dcomputecodegenerator.cpp b/driver/dcomputecodegenerator.cpp index 534757d453..b39046a733 100644 --- a/driver/dcomputecodegenerator.cpp +++ b/driver/dcomputecodegenerator.cpp @@ -28,6 +28,20 @@ DComputeCodeGenManager::~DComputeCodeGenManager() {} DComputeTarget * DComputeCodeGenManager::createComputeTarget(const std::string &s) { + if (s.substr(0, 7) == "vulkan-") { +#if LDC_LLVM_SUPPORTED_TARGET_SPIRV && LLVM_VERSION_MAJOR >= 23 +#define VULKAN_VALID_VER_INIT 100, 110, 120, 130, 140 + const std::array valid_vulkan_versions = {{VULKAN_VALID_VER_INIT}}; + + const int v = atoi(s.c_str() + 7); + if (std::find(valid_vulkan_versions.begin(), valid_vulkan_versions.end(), v) != + valid_vulkan_versions.end()) { + return createVulkanTarget(ctx, v); + } +#else + error(Loc(), "LDC was not built with Vulkan DCompute support."); +#endif + } if (s.substr(0, 4) == "ocl-") { #if LDC_LLVM_SUPPORTED_TARGET_SPIRV #define OCL_VALID_VER_INIT 100, 110, 120, 200, 210, 220 @@ -64,9 +78,10 @@ DComputeCodeGenManager::createComputeTarget(const std::string &s) { error(Loc(), "Unrecognised or invalid DCompute targets: the format is ocl-xy0 " - "for OpenCl x.y and cuda-xy0 for CUDA CC x.y." + "for OpenCl x.y and cuda-xy0 for CUDA CC x.y, and vulkan-xy0 for Vulkan." #if LDC_LLVM_SUPPORTED_TARGET_SPIRV " Valid version strings for OpenCl are ocl-{" XSTR(OCL_VALID_VER_INIT) "}." + " Valid version strings for Vulkan are vulkan-{" XSTR(VULKAN_VALID_VER_INIT) "}." #endif #if LDC_LLVM_SUPPORTED_TARGET_NVPTX " Valid version strings for CUDA are cuda-{" XSTR(CUDA_VALID_VER_INIT) "}." diff --git a/gen/abi/spirv.cpp b/gen/abi/spirv.cpp index df8a487acc..6a01dc9b96 100644 --- a/gen/abi/spirv.cpp +++ b/gen/abi/spirv.cpp @@ -53,3 +53,12 @@ struct SPIRVTargetABI : TargetABI { }; TargetABI *createSPIRVABI() { return new SPIRVTargetABI(); } + +struct SPIRVVulkanTargetABI : SPIRVTargetABI { + + llvm::CallingConv::ID callingConv(FuncDeclaration *fdecl) override { + // The synthesised wrapper is SPIR_KERNEL + return llvm::CallingConv::SPIR_FUNC; + } +}; +TargetABI *createSPIRVVulkanABI() { return new SPIRVVulkanTargetABI(); } diff --git a/gen/abi/targets.h b/gen/abi/targets.h index 49098fe257..cfb3d25ea9 100644 --- a/gen/abi/targets.h +++ b/gen/abi/targets.h @@ -31,6 +31,8 @@ TargetABI *getRISCV64TargetABI(); TargetABI *createSPIRVABI(); +TargetABI *createSPIRVVulkanABI(); + TargetABI *getWin64TargetABI(); TargetABI *getX86_64TargetABI(); diff --git a/gen/dcompute/target.cpp b/gen/dcompute/target.cpp index 65c6f027c2..e43588cce8 100644 --- a/gen/dcompute/target.cpp +++ b/gen/dcompute/target.cpp @@ -52,7 +52,9 @@ void DComputeTarget::emit(Module *m) { void DComputeTarget::writeModule() { addMetadata(); - + gABI = abi; + gIR = _ir; + gTargetMachine = targetMachine; std::string filename; llvm::raw_string_ostream os(filename); const bool is64 = global.params.targetTriple->isArch64Bit(); diff --git a/gen/dcompute/target.h b/gen/dcompute/target.h index 6ffdbea767..4d3667dcaf 100644 --- a/gen/dcompute/target.h +++ b/gen/dcompute/target.h @@ -27,7 +27,7 @@ class DComputeTarget { public: llvm::LLVMContext &ctx; int tversion; // OpenCL or CUDA CC version:major*100 + minor*10 - enum class ID { Host = 0, OpenCL = 1, CUDA = 2 }; + enum class ID { Host = 0, OpenCL = 1, CUDA = 2, Vulkan = 3 }; ID target; // ID for codegen time conditional compilation. const char *short_name; const char *binSuffix; @@ -60,4 +60,7 @@ DComputeTarget *createCUDATarget(llvm::LLVMContext &c, int sm); #if LDC_LLVM_SUPPORTED_TARGET_SPIRV DComputeTarget *createOCLTarget(llvm::LLVMContext &c, int oclver); +#if LLVM_VERSION_MAJOR >= 23 +DComputeTarget *createVulkanTarget(llvm::LLVMContext &c, int ver); +#endif #endif diff --git a/gen/dcompute/targetVulkan.cpp b/gen/dcompute/targetVulkan.cpp new file mode 100644 index 0000000000..344be6caad --- /dev/null +++ b/gen/dcompute/targetVulkan.cpp @@ -0,0 +1,189 @@ +//===-- gen/dcompute/targetVulkan.cpp -----------------------------------------===// +// +// LDC – the LLVM D compiler +// +// Parts of this file are adapted from CodeGenFunction.cpp (Clang, LLVM). +// Therefore, this file is distributed under the LLVM license. +// See the LICENSE file for details. +//===----------------------------------------------------------------------===// + +#include "llvm/Config/llvm-config.h" + +#if LDC_LLVM_SUPPORTED_TARGET_SPIRV && LLVM_VERSION_MAJOR >= 23 +#include "dmd/id.h" +#include "dmd/identifier.h" +#include "dmd/template.h" +#include "dmd/mangle.h" +#include "dmd/module.h" +#include "gen/abi/targets.h" +#include "gen/dcompute/target.h" +#include "gen/dcompute/druntime.h" +#include "gen/logger.h" +#include "gen/optimizer.h" +#include "driver/targetmachine.h" +#include "llvm/Transforms/Scalar.h" +#include "llvm/Target/TargetMachine.h" +#include +#include + +using namespace dmd; + +namespace { +class TargetVulkan : public DComputeTarget { +public: + TargetVulkan(llvm::LLVMContext &c, int ver) + : DComputeTarget(c, ver, ID::Vulkan, "vulkan", "spv", createSPIRVVulkanABI(), + {{0, 1, 2, 3, 4}}) { + + _ir = new IRState("dcomputeTargetVulkan", ctx); + // "spirv-vulkan-foo"? foo = library, pixel, etc + std::string targTriple = "spirv1.6-unknown-vulkan1.3-compute"; + _ir->module.setTargetTriple(llvm::Triple(targTriple)); + + auto floatABI = ::FloatABI::Hard; + targetMachine = createTargetMachine( + targTriple, "spirv", "", {}, + ExplicitBitness::None, floatABI, + llvm::Reloc::Static, llvm::CodeModel::Medium, codeGenOptLevel(), false); + + _ir->module.setDataLayout(targetMachine->createDataLayout()); + + _ir->dcomputetarget = this; + } + + void addMetadata() override {} + + llvm::AttrBuilder buildKernAttrs(StructLiteralExp *kernAttr) { + auto b = llvm::AttrBuilder(ctx); + b.addAttribute("hlsl.shader", "compute"); + Expressions* elts = static_cast((*(kernAttr->elements))[0])->elements; + std::string numthreads = ""; + numthreads += std::to_string((*elts)[0]->toInteger()) + ","; + numthreads += std::to_string((*elts)[1]->toInteger()) + ","; + numthreads += std::to_string((*elts)[2]->toInteger()); + + b.addAttribute("hlsl.numthreads", numthreads); + // ? "hlsl.wavesize"="8,128,64" + // ? "hlsl.export" + return b; + } + llvm::Function *buildFunction(FuncDeclaration *fd) { + auto *void_func_void = llvm::FunctionType::get(llvm::Type::getVoidTy(ctx),{}, false); + auto linkage = llvm::GlobalValue::LinkageTypes::ExternalLinkage; + auto name = llvm::Twine(mangleExact(fd)) + llvm::Twine("_kernel"); + auto *f = llvm::Function::Create(void_func_void, linkage, name, _ir->module); + f->setCallingConv(llvm::CallingConv::SPIR_KERNEL); + return f; + } + llvm::Type *buildArgType(llvm::Function *llf, llvm::SmallVector &args, llvm::StringRef name) { + IF_LOG { + Logger::cout() << "buildArgType: " << *llf << std::endl; + } + llvm::FunctionType *tf = llf->getFunctionType(); + for (unsigned int i = 0; i < tf->getNumParams(); i++) { + llvm::Type *t = tf->getParamType(i); + if (t->isPointerTy()){ + unsigned ptrSize = _ir->module.getDataLayout().getPointerSizeInBits(); + t = (ptrSize == 32)?getI32Type():getI64Type(); + } + args[i] = t; + } + + IF_LOG { + for (auto *arg : args) { + Logger::cout() << *arg; + } + } + return llvm::StructType::create(ctx, args, name); + } + llvm::TargetExtType *buildTargetType(llvm::Type *argType) { + // TODO: Do we need to bother with a "spirv.Layout" here? + //auto *layout = llvm::TargetExtType::get(ctx, "spirv.Layout", ElemType,{}); + auto * ArrayType = llvm::ArrayType::get(argType, 0); + return llvm::TargetExtType::get(ctx, "spirv.VulkanBuffer", + {ArrayType}, + {12/*StorageClass*/, 0 /*isWritable*/}); + } + + llvm::Value *buildIntrinsicCall(IRBuilder<>& builder, llvm::StringRef dbg,llvm::StringRef name, + llvm::ArrayRef types, llvm::ArrayRef args) { + IF_LOG { + Logger::println("buildIntrinsicCall: %s", name.data()); + } + LOG_SCOPE + llvm::Function *intrinsic = llvm::Intrinsic::getOrInsertDeclaration(&_ir->module, + llvm::Intrinsic::lookupIntrinsicID(name), + types); + IF_LOG { + Logger::cout() << "intrinsic = " << *intrinsic << std::endl; + Logger::println("args:"); + LOG_SCOPE + for (auto* arg : args) { + Logger::cout() << *arg << std::endl; + } + } + + return builder.CreateCall(intrinsic->getFunctionType(), intrinsic, args, dbg); + } + + void addKernelMetadata(FuncDeclaration *fd, llvm::Function *llf, StructLiteralExp *kernAttr) override { + // Fake being HLSL + llvm::Function *f = buildFunction(fd); + f->addFnAttrs(buildKernAttrs(kernAttr)); + + llvm::SmallVector argTypes(llf->getFunctionType()->getNumParams()); + auto name = llvm::Twine(mangleExact(fd)) + llvm::Twine("_args"); + auto *argType = buildArgType(llf, argTypes, name.str()); + llvm::Type *targetType = buildTargetType(argType); + + auto bb = llvm::BasicBlock::Create(ctx, "", f); + llvm::IRBuilder<> builder(ctx); + builder.SetInsertPoint(bb); + + llvm::Value *i32zero = llvm::ConstantInt::get(getI32Type(), 0, false); + llvm::Value *i32one = llvm::ConstantInt::get(getI32Type(), 1, false); + llvm::Value *i1false = llvm::ConstantInt::get(llvm::Type::getInt1Ty(ctx), 0, false); + + // We can't use `DtoConstCString` here because it ends up in the wrong address space, So we use + // `getCachedStringLiteral` directly with an explicitly supplied addrspace of `0`. + // FIXME: call should have `notnull` attribute on pointer? + auto *handle = buildIntrinsicCall(builder, "handle","llvm.spv.resource.handlefrombinding", + {targetType}, + {i32zero, i32zero, i32one, i32zero, _ir->getCachedStringLiteral(name.str(), 0) }); + auto *p11 = llvm::PointerType::get(ctx, 11); + auto *pointer = buildIntrinsicCall(builder, "pointer", "llvm.spv.resource.getpointer", + {p11, targetType, i32zero->getType()}, {handle, i32zero}); + llvm::FunctionType *tf = llf->getFunctionType(); + IF_LOG { + Logger::cout() << "load pointer: " << *pointer << std::endl; + Logger::cout() << _ir->module.getDataLayout().getABITypeAlign(argType).value() << std::endl; + Logger::cout() << tf->getParamType(0)->getTypeID() << std::endl; + Logger::cout() << "done" << std::endl; + } + LOG_SCOPE + llvm::SmallVector args(tf->getNumParams()); + + for (unsigned int i = 0; i < tf->getNumParams(); i++) { + llvm::Value *gep = builder.CreateStructGEP(argType, pointer, i); + llvm::Type *fieldTy = argType->getStructElementType(i); + + args[i] = builder.CreateAlignedLoad(fieldTy, gep, _ir->module.getDataLayout().getABITypeAlign(fieldTy), false); + + llvm::Type *t = tf->getParamType(i); + if (t->isPointerTy()) + args[i] = builder.CreateIntToPtr(args[i],t); + } + + builder.CreateCall(llf->getFunctionType(), llf, args); + builder.CreateRetVoid(); + IF_LOG Logger::cout() << *f << std::endl; + } + +}; +} // anonymous namespace. + +DComputeTarget *createVulkanTarget(llvm::LLVMContext &c, int ver) { + return new TargetVulkan(c, ver); +} + +#endif // LDC_LLVM_SUPPORTED_TARGET_SPIRV diff --git a/runtime/druntime/src/ldc/dcompute.d b/runtime/druntime/src/ldc/dcompute.d index 04002a43d9..f58a302163 100644 --- a/runtime/druntime/src/ldc/dcompute.d +++ b/runtime/druntime/src/ldc/dcompute.d @@ -14,6 +14,7 @@ enum ReflectTarget : uint Host = 0, OpenCL = 1, CUDA = 2, + Vulkan = 3, } /** * The pseudo conditional compilation function.