blob: 0e4f6e8d233388c74814daeccd2c9956eb5db041 [file] [edit]
//===- DXILResourceAccess.cpp - Resource access via load/store ------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "DXILResourceAccess.h"
#include "DirectX.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/Analysis/DXILResource.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/Analysis/VectorUtils.h"
#include "llvm/Frontend/HLSL/HLSLResource.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/IntrinsicsDirectX.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/ProfDataUtils.h"
#include "llvm/IR/User.h"
#include "llvm/IR/ValueHandle.h"
#include "llvm/InitializePasses.h"
#include "llvm/Support/DXILABI.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/KnownBits.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/Local.h"
#include "llvm/Transforms/Utils/ValueMapper.h"
#include <optional>
#define DEBUG_TYPE "dxil-resource-access"
using namespace llvm;
[[noreturn]] static void
diagnoseNonUniqueResourceAccess(Instruction *I,
ArrayRef<IntrinsicInst *> Handles) {
LLVMContext &Context = I->getContext();
std::string InstStr;
raw_string_ostream InstOS(InstStr);
I->print(InstOS);
Context.diagnose(
DiagnosticInfoGeneric("At resource access:" + Twine(InstStr), DS_Note));
for (auto *Handle : Handles) {
std::string HandleStr;
raw_string_ostream HandleOS(HandleStr);
Handle->print(HandleOS);
Context.diagnose(DiagnosticInfoGeneric(
"Uses resource handle:" + Twine(HandleStr), DS_Note));
}
report_fatal_error(
"Resource access is not guaranteed to map to a unique global resource",
/*gen_crash_diag=*/false);
}
static Value *traverseGEPOffsets(const DataLayout &DL, IRBuilder<> &Builder,
Value *Ptr, uint64_t AccessSize) {
Value *Offset = nullptr;
while (Ptr) {
if ([[maybe_unused]] auto *II = dyn_cast<IntrinsicInst>(Ptr)) {
assert((II->getIntrinsicID() == Intrinsic::dx_resource_getpointer ||
II->getIntrinsicID() == Intrinsic::dx_resource_getbasepointer) &&
"Resource access through unexpected intrinsic");
return Offset ? Offset : ConstantInt::get(Builder.getInt32Ty(), 0);
}
auto *GEP = dyn_cast<GetElementPtrInst>(Ptr);
assert(GEP && "Resource access through unexpected instruction");
unsigned NumIndices = GEP->getNumIndices();
uint64_t IndexScale = DL.getTypeAllocSize(GEP->getSourceElementType());
APInt ConstantOffset(DL.getIndexTypeSizeInBits(GEP->getType()), 0);
Value *GEPOffset;
if (GEP->accumulateConstantOffset(DL, ConstantOffset)) {
// We have a constant offset (in bytes).
GEPOffset =
ConstantInt::get(DL.getIndexType(GEP->getType()), ConstantOffset);
IndexScale = 1;
} else if (NumIndices == 1) {
// If we have a single index we're indexing into a top level array. This
// generally only happens with cbuffers.
GEPOffset = *GEP->idx_begin();
} else if (NumIndices == 2) {
// If we have two indices, this should be an access through a pointer.
auto *IndexIt = GEP->idx_begin();
assert(cast<ConstantInt>(IndexIt)->getZExtValue() == 0 &&
"GEP is not indexing through pointer");
GEPOffset = *(++IndexIt);
} else
llvm_unreachable("Unhandled GEP structure for resource access");
uint64_t ElemSize = AccessSize;
if (!(IndexScale % ElemSize)) {
// If our scale is an exact multiple of the access size, adjust the
// scaling to avoid an unnecessary division.
IndexScale /= ElemSize;
ElemSize = 1;
}
if (IndexScale != 1)
GEPOffset = Builder.CreateMul(
GEPOffset, ConstantInt::get(Builder.getInt32Ty(), IndexScale));
if (ElemSize != 1)
GEPOffset = Builder.CreateUDiv(
GEPOffset, ConstantInt::get(Builder.getInt32Ty(), ElemSize));
Offset = Offset ? Builder.CreateAdd(Offset, GEPOffset) : GEPOffset;
Ptr = GEP->getPointerOperand();
}
llvm_unreachable("GEP of null pointer?");
}
static void createTypedBufferStore(IntrinsicInst *II, StoreInst *SI,
dxil::ResourceTypeInfo &RTI) {
const DataLayout &DL = SI->getDataLayout();
IRBuilder<> Builder(SI);
Type *ContainedType = RTI.getHandleTy()->getTypeParameter(0);
Type *ScalarType = ContainedType->getScalarType();
Type *LoadType = StructType::get(ContainedType, Builder.getInt1Ty());
Value *V = SI->getValueOperand();
if (V->getType() == ContainedType) {
// V is already the right type.
assert(SI->getPointerOperand() == II &&
"Store of whole element has mismatched address to store to");
} else if (V->getType() == ScalarType) {
// We're storing a scalar, so we need to load the current value and only
// replace the relevant part.
auto *Load = Builder.CreateIntrinsic(
LoadType, Intrinsic::dx_resource_load_typedbuffer,
{II->getOperand(0), II->getOperand(1)});
auto *Struct = Builder.CreateExtractValue(Load, {0});
uint64_t AccessSize = DL.getTypeSizeInBits(ScalarType) / 8;
Value *Offset =
traverseGEPOffsets(DL, Builder, SI->getPointerOperand(), AccessSize);
V = Builder.CreateInsertElement(Struct, V, Offset);
} else {
llvm_unreachable("Store to typed resource has invalid type");
}
auto *Inst = Builder.CreateIntrinsic(
Builder.getVoidTy(), Intrinsic::dx_resource_store_typedbuffer,
{II->getOperand(0), II->getOperand(1), V});
SI->replaceAllUsesWith(Inst);
}
/// Build a zero-initialized offset operand matching the shape of the given
/// coordinate operand. Accesses through `operator[]` never have offsets.
static Value *getNullOffsetsFor(IRBuilder<> &Builder, Value *Coords) {
Type *CoordTy = Coords->getType();
Type *OffsetTy;
if (auto *VecTy = dyn_cast<FixedVectorType>(CoordTy))
OffsetTy =
FixedVectorType::get(Builder.getInt32Ty(), VecTy->getNumElements());
else
OffsetTy = Builder.getInt32Ty();
return Constant::getNullValue(OffsetTy);
}
static void createTextureStore(IntrinsicInst *II, StoreInst *SI,
dxil::ResourceTypeInfo &RTI) {
const DataLayout &DL = SI->getDataLayout();
IRBuilder<> Builder(SI);
Type *ContainedType = RTI.getHandleTy()->getTypeParameter(0);
Type *ScalarType = ContainedType->getScalarType();
Value *Handle = II->getOperand(0);
Value *Coords = II->getOperand(1);
Value *V = SI->getValueOperand();
if (V->getType() == ContainedType) {
// V is already the right type.
assert(SI->getPointerOperand() == II &&
"Store of whole element has mismatched address to store to");
} else if (V->getType() == ScalarType) {
// We're storing a scalar, so we need to load the current value and only
// replace the relevant part. For operator[] the mip level and the offsets
// are always zero; DXILOpLowering drops the mip level for UAVs.
Value *MipLevel = Builder.getInt32(0);
Value *Offsets = getNullOffsetsFor(Builder, Coords);
auto *Load = Builder.CreateIntrinsic(ContainedType,
Intrinsic::dx_resource_load_level,
{Handle, Coords, MipLevel, Offsets});
uint64_t AccessSize = DL.getTypeSizeInBits(ScalarType) / 8;
Value *Offset =
traverseGEPOffsets(DL, Builder, SI->getPointerOperand(), AccessSize);
V = Builder.CreateInsertElement(Load, V, Offset);
} else {
llvm_unreachable("Store to texture resource has invalid type");
}
auto *Inst = Builder.CreateIntrinsic(Builder.getVoidTy(),
Intrinsic::dx_resource_store_texture,
{Handle, Coords, V});
SI->replaceAllUsesWith(Inst);
}
static void emitRawStore(IRBuilder<> &Builder, Value *Buffer, Value *Index,
Value *Offset, Value *V, dxil::ResourceTypeInfo &RTI) {
// For raw buffer (ie, HLSL's ByteAddressBuffer), we need to fold the access
// entirely into the index.
if (!RTI.isStruct()) {
auto *ConstantOffset = dyn_cast<ConstantInt>(Offset);
if (!ConstantOffset || !ConstantOffset->isZero())
Index = Builder.CreateAdd(Index, Offset);
Offset = llvm::PoisonValue::get(Builder.getInt32Ty());
}
Builder.CreateIntrinsic(Builder.getVoidTy(),
Intrinsic::dx_resource_store_rawbuffer,
{Buffer, Index, Offset, V});
}
static void createRawStores(IntrinsicInst *II, StoreInst *SI,
dxil::ResourceTypeInfo &RTI) {
const DataLayout &DL = SI->getDataLayout();
IRBuilder<> Builder(SI);
Value *V = SI->getValueOperand();
assert(!V->getType()->isAggregateType() &&
"Resource store should be scalar or vector type");
Value *Index = II->getOperand(1);
// The offset for the rawbuffer load and store ops is always in bytes.
uint64_t AccessSize = 1;
Value *Offset =
traverseGEPOffsets(DL, Builder, SI->getPointerOperand(), AccessSize);
auto *VT = dyn_cast<FixedVectorType>(V->getType());
if (VT && VT->getNumElements() > 4) {
// Split into stores of at most 4 elements.
Type *EltTy = VT->getElementType();
Value *Stride = ConstantInt::get(Builder.getInt32Ty(),
4 * (DL.getTypeSizeInBits(EltTy) / 8));
SmallVector<int, 4> Indices;
for (unsigned int I = 0, N = VT->getNumElements(); I < N; I += 4) {
if (I > 0)
Offset = Builder.CreateAdd(Offset, Stride);
for (unsigned int J = I, E = std::min(N, J + 4); J < E; ++J)
Indices.push_back(J);
Value *Part = Builder.CreateShuffleVector(V, Indices);
emitRawStore(Builder, II->getOperand(0), Index, Offset, Part, RTI);
Indices.clear();
}
} else
emitRawStore(Builder, II->getOperand(0), Index, Offset, V, RTI);
}
static void createStoreIntrinsic(IntrinsicInst *II, StoreInst *SI,
dxil::ResourceTypeInfo &RTI) {
switch (RTI.getResourceKind()) {
case dxil::ResourceKind::TypedBuffer:
return createTypedBufferStore(II, SI, RTI);
case dxil::ResourceKind::RawBuffer:
case dxil::ResourceKind::StructuredBuffer:
return createRawStores(II, SI, RTI);
case dxil::ResourceKind::Texture1D:
case dxil::ResourceKind::Texture2D:
case dxil::ResourceKind::Texture3D:
case dxil::ResourceKind::Texture1DArray:
case dxil::ResourceKind::Texture2DArray:
return createTextureStore(II, SI, RTI);
case dxil::ResourceKind::Texture2DMS:
case dxil::ResourceKind::Texture2DMSArray:
case dxil::ResourceKind::TextureCube:
case dxil::ResourceKind::TextureCubeArray:
case dxil::ResourceKind::FeedbackTexture2D:
case dxil::ResourceKind::FeedbackTexture2DArray:
reportFatalUsageError(
"DXIL Store not implemented for this texture resource kind");
return;
case dxil::ResourceKind::CBuffer:
case dxil::ResourceKind::Sampler:
case dxil::ResourceKind::TBuffer:
case dxil::ResourceKind::RTAccelerationStructure:
case dxil::ResourceKind::Invalid:
case dxil::ResourceKind::NumEntries:
llvm_unreachable("Invalid resource kind for store");
}
llvm_unreachable("Unhandled case in switch");
}
static std::optional<dxil::AtomicBinOpCode>
getAtomicBinOpCode(AtomicRMWInst::BinOp BinOp) {
switch (BinOp) {
case AtomicRMWInst::Add:
return dxil::AtomicBinOpCode::Add;
case AtomicRMWInst::And:
return dxil::AtomicBinOpCode::And;
case AtomicRMWInst::Or:
return dxil::AtomicBinOpCode::Or;
case AtomicRMWInst::Xor:
return dxil::AtomicBinOpCode::Xor;
case AtomicRMWInst::Min:
return dxil::AtomicBinOpCode::IMin;
case AtomicRMWInst::Max:
return dxil::AtomicBinOpCode::IMax;
case AtomicRMWInst::UMin:
return dxil::AtomicBinOpCode::UMin;
case AtomicRMWInst::UMax:
return dxil::AtomicBinOpCode::UMax;
case AtomicRMWInst::Xchg:
return dxil::AtomicBinOpCode::Exchange;
case AtomicRMWInst::Sub:
case AtomicRMWInst::Nand:
case AtomicRMWInst::FAdd:
case AtomicRMWInst::FSub:
case AtomicRMWInst::FMax:
case AtomicRMWInst::FMin:
case AtomicRMWInst::FMaximum:
case AtomicRMWInst::FMinimum:
case AtomicRMWInst::FMaximumNum:
case AtomicRMWInst::FMinimumNum:
case AtomicRMWInst::UIncWrap:
case AtomicRMWInst::UDecWrap:
case AtomicRMWInst::USubCond:
case AtomicRMWInst::USubSat:
case AtomicRMWInst::BAD_BINOP:
return std::nullopt;
}
llvm_unreachable("Unhandled atomicrmw operation");
}
// Compute the (coord0, coord1) pair for a buffer resource atomic operation.
// Non-struct buffers (RawBuffer or TypedBuffer) fold the byte offset into the
// index and leave coord1 poison. Only StructuredBuffer atomics use both a
// struct index and a byte offset.
static std::pair<Value *, Value *>
getAtomicResourceCoords(IntrinsicInst *II, Value *PointerOperand,
dxil::ResourceTypeInfo &RTI, IRBuilder<> &Builder,
const DataLayout &DL) {
Value *Index = II->getOperand(1);
// The offset for the rawbuffer load/store/atomic ops is always in bytes.
uint64_t AccessSize = 1;
Value *Offset = traverseGEPOffsets(DL, Builder, PointerOperand, AccessSize);
if (!RTI.isStruct()) {
auto *ConstantOffset = dyn_cast<ConstantInt>(Offset);
if (!ConstantOffset || !ConstantOffset->isZero())
Index = Builder.CreateAdd(Index, Offset);
Offset = llvm::PoisonValue::get(Builder.getInt32Ty());
}
return {Index, Offset};
}
// The coordinates of a texture access are a scalar or a vector with one element
// per texture dimension, including the array slice if there is one. These map
// directly onto the coordinate operands of the atomic ops.
static SmallVector<Value *, 3> getTextureAtomicCoords(IntrinsicInst *II,
IRBuilder<> &Builder) {
Value *Coords = II->getOperand(1);
SmallVector<Value *, 3> CoordArgs;
if (auto *VecTy = dyn_cast<FixedVectorType>(Coords->getType())) {
assert(VecTy->getNumElements() <= 3 && "Too many texture coordinates");
for (unsigned I = 0, E = VecTy->getNumElements(); I != E; ++I)
CoordArgs.push_back(Builder.CreateExtractElement(Coords, I));
} else {
CoordArgs.push_back(Coords);
}
return CoordArgs;
}
static void emitAtomicBinOp(IRBuilder<> &Builder, AtomicRMWInst *AI,
Value *Handle, ArrayRef<Value *> Coords) {
assert(!Coords.empty() && Coords.size() <= 3 &&
"Atomic operations take between one and three coordinates");
std::optional<dxil::AtomicBinOpCode> BinOpCode =
getAtomicBinOpCode(AI->getOperation());
if (!BinOpCode) {
reportFatalUsageError("DXIL resource atomicrmw operation not implemented");
return;
}
// DXIL has no floating-point atomic op. A float exchange only moves the bit
// pattern, so cast the value to an integer of the same width, exchange, and
// cast the result back. This matches what DXC emits.
Value *Val = AI->getValOperand();
Type *ValTy = Val->getType();
Type *OpTy = ValTy;
if (ValTy->isFloatingPointTy()) {
OpTy = Builder.getIntNTy(ValTy->getPrimitiveSizeInBits());
Val = Builder.CreateBitCast(Val, OpTy);
}
SmallVector<Value *, 6> Args{
Handle, Builder.getInt32(static_cast<uint32_t>(*BinOpCode))};
append_range(Args, Coords);
Args.append(3 - Coords.size(), PoisonValue::get(Builder.getInt32Ty()));
Args.push_back(Val);
// Emit the target-independent intrinsic; DXILOpLowering lowers it to the
// DXIL `AtomicBinOp` op and handles the target-ext-typed handle cast via
// its `createTmpHandleCast` bookkeeping.
Value *Result =
Builder.CreateIntrinsic(OpTy, Intrinsic::dx_resource_atomic_binop, Args);
if (OpTy != ValTy)
Result = Builder.CreateBitCast(Result, ValTy);
AI->replaceAllUsesWith(Result);
}
static void createBufferAtomicBinOp(IntrinsicInst *II, AtomicRMWInst *AI,
dxil::ResourceTypeInfo &RTI) {
const DataLayout &DL = AI->getDataLayout();
IRBuilder<> Builder(AI);
auto [Index, Offset] =
getAtomicResourceCoords(II, AI->getPointerOperand(), RTI, Builder, DL);
emitAtomicBinOp(Builder, AI, II->getOperand(0), {Index, Offset});
}
static void createTextureAtomicBinOp(IntrinsicInst *II, AtomicRMWInst *AI,
dxil::ResourceTypeInfo &RTI) {
// A texture atomic operates on a whole texel, so a multi-component texel has
// no single addressable component. A scalar float texel is allowed, because
// emitAtomicBinOp exchanges its bit pattern as an integer.
Type *ContainedType = RTI.getHandleTy()->getTypeParameter(0);
if (!ContainedType->isIntegerTy() && !ContainedType->isFloatingPointTy()) {
reportFatalUsageError("DXIL atomicrmw requires a texture resource with a "
"scalar element type");
return;
}
IRBuilder<> Builder(AI);
emitAtomicBinOp(Builder, AI, II->getOperand(0),
getTextureAtomicCoords(II, Builder));
}
static void emitAtomicCompareExchange(IRBuilder<> &Builder,
AtomicCmpXchgInst *AI, Value *Handle,
ArrayRef<Value *> Coords) {
assert(!Coords.empty() && Coords.size() <= 3 &&
"Atomic operations take between one and three coordinates");
Value *Compare = AI->getCompareOperand();
Value *NewValue = AI->getNewValOperand();
SmallVector<Value *, 6> Args{Handle};
append_range(Args, Coords);
Args.append(3 - Coords.size(), PoisonValue::get(Builder.getInt32Ty()));
Args.push_back(Compare);
Args.push_back(NewValue);
Value *Original = Builder.CreateIntrinsic(
NewValue->getType(), Intrinsic::dx_resource_atomic_compare_exchange,
Args);
// `cmpxchg` yields a { original, success } pair, but the DXIL op returns
// only the original value. DXIL has no way to express the success flag, and
// no HLSL builtin reads it, so replace the users of the pair directly
// instead of building it again. No pass after this one removes dead code.
SmallVector<ExtractValueInst *> Extracts;
for (User *U : AI->users()) {
auto *EV = dyn_cast<ExtractValueInst>(U);
if (!EV || EV->getIndices()[0] != 0)
reportFatalUsageError("DXIL cmpxchg provides only the original value");
Extracts.push_back(EV);
}
for (ExtractValueInst *EV : Extracts) {
EV->replaceAllUsesWith(Original);
EV->eraseFromParent();
}
}
static void createBufferAtomicCompareExchange(IntrinsicInst *II,
AtomicCmpXchgInst *AI,
dxil::ResourceTypeInfo &RTI) {
const DataLayout &DL = AI->getDataLayout();
IRBuilder<> Builder(AI);
auto [Index, Offset] =
getAtomicResourceCoords(II, AI->getPointerOperand(), RTI, Builder, DL);
emitAtomicCompareExchange(Builder, AI, II->getOperand(0), {Index, Offset});
}
// `cmpxchg` operands are always integers, so unlike atomicrmw there is no
// float element type to convert here. The element type must be scalar.
static void createTextureAtomicCompareExchange(IntrinsicInst *II,
AtomicCmpXchgInst *AI,
dxil::ResourceTypeInfo &RTI) {
Type *ContainedType = RTI.getHandleTy()->getTypeParameter(0);
if (!ContainedType->isIntegerTy() && !ContainedType->isFloatingPointTy()) {
reportFatalUsageError("DXIL cmpxchg requires a texture resource with a "
"scalar element type");
return;
}
IRBuilder<> Builder(AI);
emitAtomicCompareExchange(Builder, AI, II->getOperand(0),
getTextureAtomicCoords(II, Builder));
}
static void createAtomicBinOpIntrinsic(IntrinsicInst *II, AtomicRMWInst *AI,
dxil::ResourceTypeInfo &RTI) {
switch (RTI.getResourceKind()) {
case dxil::ResourceKind::TypedBuffer:
case dxil::ResourceKind::RawBuffer:
case dxil::ResourceKind::StructuredBuffer:
return createBufferAtomicBinOp(II, AI, RTI);
case dxil::ResourceKind::Texture1D:
case dxil::ResourceKind::Texture2D:
case dxil::ResourceKind::Texture3D:
case dxil::ResourceKind::Texture1DArray:
case dxil::ResourceKind::Texture2DArray:
return createTextureAtomicBinOp(II, AI, RTI);
case dxil::ResourceKind::Texture2DMS:
case dxil::ResourceKind::Texture2DMSArray:
case dxil::ResourceKind::TextureCube:
case dxil::ResourceKind::TextureCubeArray:
case dxil::ResourceKind::FeedbackTexture2D:
case dxil::ResourceKind::FeedbackTexture2DArray:
reportFatalUsageError(
"DXIL atomicrmw not implemented for this texture resource kind");
return;
case dxil::ResourceKind::CBuffer:
case dxil::ResourceKind::Sampler:
case dxil::ResourceKind::TBuffer:
reportFatalUsageError(
"DXIL atomicrmw not implemented for this resource type");
return;
case dxil::ResourceKind::RTAccelerationStructure:
case dxil::ResourceKind::Invalid:
case dxil::ResourceKind::NumEntries:
llvm_unreachable("Invalid resource kind for atomicrmw");
}
llvm_unreachable("Unhandled case in switch");
}
static void createAtomicCompareExchangeIntrinsic(IntrinsicInst *II,
AtomicCmpXchgInst *AI,
dxil::ResourceTypeInfo &RTI) {
switch (RTI.getResourceKind()) {
case dxil::ResourceKind::TypedBuffer:
case dxil::ResourceKind::RawBuffer:
case dxil::ResourceKind::StructuredBuffer:
return createBufferAtomicCompareExchange(II, AI, RTI);
case dxil::ResourceKind::Texture1D:
case dxil::ResourceKind::Texture2D:
case dxil::ResourceKind::Texture3D:
case dxil::ResourceKind::Texture1DArray:
case dxil::ResourceKind::Texture2DArray:
return createTextureAtomicCompareExchange(II, AI, RTI);
case dxil::ResourceKind::Texture2DMS:
case dxil::ResourceKind::Texture2DMSArray:
case dxil::ResourceKind::TextureCube:
case dxil::ResourceKind::TextureCubeArray:
case dxil::ResourceKind::FeedbackTexture2D:
case dxil::ResourceKind::FeedbackTexture2DArray:
reportFatalUsageError(
"DXIL cmpxchg not implemented for this texture resource kind");
return;
case dxil::ResourceKind::CBuffer:
case dxil::ResourceKind::Sampler:
case dxil::ResourceKind::TBuffer:
reportFatalUsageError(
"DXIL cmpxchg not implemented for this resource type");
return;
case dxil::ResourceKind::RTAccelerationStructure:
case dxil::ResourceKind::Invalid:
case dxil::ResourceKind::NumEntries:
llvm_unreachable("Invalid resource kind for cmpxchg");
}
llvm_unreachable("Unhandled case in switch");
}
static void createTypedBufferLoad(IntrinsicInst *II, LoadInst *LI,
dxil::ResourceTypeInfo &RTI) {
const DataLayout &DL = LI->getDataLayout();
IRBuilder<> Builder(LI);
Type *ContainedType = RTI.getHandleTy()->getTypeParameter(0);
Type *LoadType = StructType::get(ContainedType, Builder.getInt1Ty());
Value *V =
Builder.CreateIntrinsic(LoadType, Intrinsic::dx_resource_load_typedbuffer,
{II->getOperand(0), II->getOperand(1)});
V = Builder.CreateExtractValue(V, {0});
Type *ScalarType = ContainedType->getScalarType();
uint64_t AccessSize = DL.getTypeSizeInBits(ScalarType) / 8;
Value *Offset =
traverseGEPOffsets(DL, Builder, LI->getPointerOperand(), AccessSize);
auto *ConstantOffset = dyn_cast<ConstantInt>(Offset);
if (!ConstantOffset || !ConstantOffset->isZero())
V = Builder.CreateExtractElement(V, Offset);
// If we loaded a <1 x ...> instead of a scalar (presumably to feed a
// shufflevector), then make sure we're maintaining the resulting type.
if (auto *VT = dyn_cast<FixedVectorType>(LI->getType()))
if (VT->getNumElements() == 1 && !isa<FixedVectorType>(V->getType()))
V = Builder.CreateInsertElement(PoisonValue::get(VT), V,
Builder.getInt32(0));
LI->replaceAllUsesWith(V);
}
static void createTextureLoad(IntrinsicInst *II, LoadInst *LI,
dxil::ResourceTypeInfo &RTI) {
const DataLayout &DL = LI->getDataLayout();
IRBuilder<> Builder(LI);
Type *ContainedType = RTI.getHandleTy()->getTypeParameter(0);
Value *Handle = II->getOperand(0);
Value *Coords = II->getOperand(1);
// For operator[], mip level is 0.
Value *MipLevel = Builder.getInt32(0);
// For operator[], offsets are zero.
Value *Offsets = getNullOffsetsFor(Builder, Coords);
Value *V =
Builder.CreateIntrinsic(ContainedType, Intrinsic::dx_resource_load_level,
{Handle, Coords, MipLevel, Offsets});
Type *ScalarType = ContainedType->getScalarType();
uint64_t AccessSize = DL.getTypeSizeInBits(ScalarType) / 8;
Value *Offset =
traverseGEPOffsets(DL, Builder, LI->getPointerOperand(), AccessSize);
auto *ConstantOffset = dyn_cast<ConstantInt>(Offset);
if (!ConstantOffset || !ConstantOffset->isZero())
V = Builder.CreateExtractElement(V, Offset);
// If we loaded a <1 x ...> instead of a scalar (presumably to feed a
// shufflevector), then make sure we're maintaining the resulting type.
if (auto *VT = dyn_cast<FixedVectorType>(LI->getType()))
if (VT->getNumElements() == 1 && !isa<FixedVectorType>(V->getType()))
V = Builder.CreateInsertElement(PoisonValue::get(VT), V,
Builder.getInt32(0));
LI->replaceAllUsesWith(V);
}
static Value *emitRawLoad(IRBuilder<> &Builder, Type *Ty, Value *Buffer,
Value *Index, Value *Offset,
dxil::ResourceTypeInfo &RTI) {
// For raw buffer (ie, HLSL's ByteAddressBuffer), we need to fold the access
// entirely into the index.
if (!RTI.isStruct()) {
auto *ConstantOffset = dyn_cast<ConstantInt>(Offset);
if (!ConstantOffset || !ConstantOffset->isZero())
Index = Builder.CreateAdd(Index, Offset);
Offset = llvm::PoisonValue::get(Builder.getInt32Ty());
}
// The load intrinsic includes the bit for CheckAccessFullyMapped, so we need
// to add that to the return type.
Type *TypeWithCheck = StructType::get(Ty, Builder.getInt1Ty());
Value *V = Builder.CreateIntrinsic(TypeWithCheck,
Intrinsic::dx_resource_load_rawbuffer,
{Buffer, Index, Offset});
return Builder.CreateExtractValue(V, {0});
}
static void createRawLoads(IntrinsicInst *II, LoadInst *LI,
dxil::ResourceTypeInfo &RTI) {
const DataLayout &DL = LI->getDataLayout();
IRBuilder<> Builder(LI);
Value *Index = II->getOperand(1);
// The offset for the rawbuffer load and store ops is always in bytes.
uint64_t AccessSize = 1;
Value *Offset =
traverseGEPOffsets(DL, Builder, LI->getPointerOperand(), AccessSize);
// TODO: We could make this handle aggregates by walking the structure and
// handling each field individually, but we don't ever generate code that
// would hit that so it seems superfluous.
assert(!LI->getType()->isAggregateType() &&
"Resource load should be scalar or vector type");
Value *V;
if (auto *VT = dyn_cast<FixedVectorType>(LI->getType())) {
// Split into loads of at most 4 elements.
Type *EltTy = VT->getElementType();
Value *Stride = ConstantInt::get(Builder.getInt32Ty(),
4 * (DL.getTypeSizeInBits(EltTy) / 8));
SmallVector<Value *> Parts;
for (unsigned int I = 0, N = VT->getNumElements(); I < N; I += 4) {
Type *Ty = FixedVectorType::get(EltTy, N - I < 4 ? N - I : 4);
if (I > 0)
Offset = Builder.CreateAdd(Offset, Stride);
Parts.push_back(
emitRawLoad(Builder, Ty, II->getOperand(0), Index, Offset, RTI));
}
V = Parts.size() > 1 ? concatenateVectors(Builder, Parts) : Parts[0];
} else
V = emitRawLoad(Builder, LI->getType(), II->getOperand(0), Index, Offset,
RTI);
LI->replaceAllUsesWith(V);
}
namespace {
/// Helper for building a `load.cbufferrow` intrinsic given a simple type.
struct CBufferRowIntrin {
Intrinsic::ID IID;
Type *RetTy;
unsigned int EltSize;
unsigned int NumElts;
CBufferRowIntrin(const DataLayout &DL, Type *Ty) {
assert(Ty == Ty->getScalarType() && "Expected scalar type");
switch (DL.getTypeSizeInBits(Ty)) {
case 16:
IID = Intrinsic::dx_resource_load_cbufferrow_8;
RetTy = StructType::get(Ty, Ty, Ty, Ty, Ty, Ty, Ty, Ty);
EltSize = 2;
NumElts = 8;
break;
case 32:
IID = Intrinsic::dx_resource_load_cbufferrow_4;
RetTy = StructType::get(Ty, Ty, Ty, Ty);
EltSize = 4;
NumElts = 4;
break;
case 64:
IID = Intrinsic::dx_resource_load_cbufferrow_2;
RetTy = StructType::get(Ty, Ty);
EltSize = 8;
NumElts = 2;
break;
default:
llvm_unreachable("Only 16, 32, and 64 bit types supported");
}
}
};
} // namespace
static Value *loadCBufferRowIntoWindow(IRBuilder<> &Builder,
const CBufferRowIntrin &Intrin,
Value *Handle, Value *FirstRow,
Value *Window, unsigned RowNumber,
StringRef Name) {
Value *RowIndex =
RowNumber == 0 ? FirstRow
: Builder.CreateAdd(FirstRow, Builder.getInt32(RowNumber));
Value *RowLoad = Builder.CreateIntrinsic(
Intrin.RetTy, Intrin.IID, {Handle, RowIndex}, nullptr, Name + ".load");
for (unsigned J = 0; J != Intrin.NumElts; ++J) {
Value *Element = Builder.CreateExtractValue(RowLoad, {J});
Window = Builder.CreateInsertElement(
Window, Element, Builder.getInt32(RowNumber * Intrin.NumElts + J));
}
return Window;
}
static Value *createDynamicCBufferLoad(IntrinsicInst *II, LoadInst *LI,
const CBufferRowIntrin &Intrin,
Value *BaseRow, Value *ByteOffset,
const KnownBits &WithinRow) {
IRBuilder<> Builder(LI);
Type *Ty = LI->getType();
auto *VT = dyn_cast<FixedVectorType>(Ty);
unsigned NumElements = VT ? VT->getNumElements() : 1;
StringRef Name = LI->getName();
Value *Handle = II->getOperand(0);
Value *RowSize = Builder.getInt32(hlsl::CBufferRowSizeInBytes);
Value *FirstRow =
Builder.CreateAdd(BaseRow, Builder.CreateUDiv(ByteOffset, RowSize));
Value *Component = Builder.CreateUDiv(Builder.CreateURem(ByteOffset, RowSize),
Builder.getInt32(Intrin.EltSize));
unsigned MinComponent =
WithinRow.getMinValue().getZExtValue() / Intrin.EltSize;
unsigned MaxComponent =
WithinRow.getMaxValue().getZExtValue() / Intrin.EltSize;
unsigned MinRows = 1 + (MinComponent + NumElements - 1) / Intrin.NumElts;
unsigned MaxRows = 1 + (MaxComponent + NumElements - 1) / Intrin.NumElts;
auto *WindowTy =
FixedVectorType::get(Ty->getScalarType(), MaxRows * Intrin.NumElts);
Value *Window = PoisonValue::get(WindowTy);
for (unsigned I = 0; I != MinRows; ++I)
Window = loadCBufferRowIntoWindow(Builder, Intrin, Handle, FirstRow, Window,
I, Name);
// The unknown starting component can require at most one extra row.
// Load it only when needed, rather than accessing beyond the load.
if (MaxRows != MinRows) {
BasicBlock *Head = LI->getParent();
Value *LastComponent =
Builder.CreateAdd(Component, Builder.getInt32(NumElements - 1));
Value *CrossesRow = Builder.CreateICmpUGE(
LastComponent, Builder.getInt32(MinRows * Intrin.NumElts));
Instruction *ThenTerm = SplitBlockAndInsertIfThen(CrossesRow, LI, false);
setExplicitlyUnknownBranchWeightsIfProfiled(*Head->getTerminator(),
DEBUG_TYPE);
Builder.SetInsertPoint(ThenTerm);
Value *NextWindow = loadCBufferRowIntoWindow(
Builder, Intrin, Handle, FirstRow, Window, MinRows, Name);
Builder.SetInsertPoint(LI);
auto *Rows = Builder.CreatePHI(WindowTy, 2, Name + ".rows");
Rows->addIncoming(Window, Head);
Rows->addIncoming(NextWindow, ThenTerm->getParent());
Window = Rows;
}
Value *Result = PoisonValue::get(Ty);
for (unsigned I = 0; I != NumElements; ++I) {
Value *Index =
I == 0 ? Component : Builder.CreateAdd(Component, Builder.getInt32(I));
Value *Element =
Builder.CreateExtractElement(Window, Index, Name + ".extract");
Result =
VT ? Builder.CreateInsertElement(Result, Element, Builder.getInt32(I))
: Element;
}
return Result;
}
static KnownBits computeCBufferOffsetKnownBits(Value *Offset,
const DataLayout &DL,
const Instruction *CxtI) {
KnownBits Known = computeKnownBits(Offset, DL, nullptr, CxtI);
auto *Phi = dyn_cast<PHINode>(Offset);
if (!Phi || Phi->getNumIncomingValues() == 0)
return Known;
// PHI queries limit recursion into each input, which can hide row alignment.
KnownBits IncomingKnown(Known.getBitWidth());
IncomingKnown.setAllConflict();
for (unsigned I = 0; I != Phi->getNumIncomingValues(); ++I)
IncomingKnown = IncomingKnown.intersectWith(
computeKnownBits(Phi->getIncomingValue(I), DL, nullptr,
Phi->getIncomingBlock(I)->getTerminator()));
return Known.unionWith(IncomingKnown);
}
static void createCBufferLoad(IntrinsicInst *II, LoadInst *LI,
dxil::ResourceTypeInfo &RTI) {
const DataLayout &DL = LI->getDataLayout();
Type *Ty = LI->getType();
assert(!isa<StructType>(Ty) && "Structs not handled yet");
CBufferRowIntrin Intrin(DL, Ty->getScalarType());
StringRef Name = LI->getName();
Value *Handle = II->getOperand(0);
IRBuilder<> Builder(LI);
ConstantInt *GlobalOffset =
II->getIntrinsicID() == Intrinsic::dx_resource_getbasepointer
? ConstantInt::get(Builder.getInt32Ty(), 0)
: dyn_cast<ConstantInt>(II->getOperand(1));
assert(GlobalOffset && "CBuffer getpointer index must be constant");
uint64_t GlobalOffsetVal = GlobalOffset->getZExtValue();
Value *CurrentRow = ConstantInt::get(
Builder.getInt32Ty(), GlobalOffsetVal / hlsl::CBufferRowSizeInBytes);
unsigned int CurrentIndex =
(GlobalOffsetVal % hlsl::CBufferRowSizeInBytes) / Intrin.EltSize;
// Every object in a cbuffer either fits in a row or is aligned to a row. This
// means that only the very last pointer access can point into a row.
auto *LastGEP = dyn_cast<GEPOperator>(LI->getPointerOperand());
if (!LastGEP) {
// If we don't have a GEP at all we're just accessing the resource through
// the result of getpointer directly.
assert(LI->getPointerOperand() == II &&
"Unexpected indirect access to resource without GEP");
} else {
Value *GEPOffset = traverseGEPOffsets(
DL, Builder, LastGEP->getPointerOperand(), hlsl::CBufferRowSizeInBytes);
CurrentRow = Builder.CreateAdd(GEPOffset, CurrentRow);
APInt ConstantOffset(DL.getIndexTypeSizeInBits(LastGEP->getType()), 0);
if (LastGEP->accumulateConstantOffset(DL, ConstantOffset)) {
APInt Remainder(DL.getIndexTypeSizeInBits(LastGEP->getType()),
hlsl::CBufferRowSizeInBytes);
APInt::udivrem(ConstantOffset, Remainder, ConstantOffset, Remainder);
CurrentRow = Builder.CreateAdd(
CurrentRow, ConstantInt::get(Builder.getInt32Ty(), ConstantOffset));
CurrentIndex += Remainder.udiv(Intrin.EltSize).getZExtValue();
} else {
assert(LastGEP->getNumIndices() == 1 &&
"Last GEP of cbuffer access is not array or struct access");
// Aggregate strides are row-aligned, but merged byte offsets can select
// different components within a row.
Value *RowIndex = Builder.CreateSExtOrTrunc(*LastGEP->idx_begin(),
Builder.getInt32Ty());
uint64_t IndexScale =
DL.getTypeAllocSize(LastGEP->getSourceElementType());
if (IndexScale < hlsl::CBufferRowSizeInBytes) {
assert(hlsl::CBufferRowSizeInBytes % IndexScale == 0 &&
"CBuffer GEP index is not row aligned");
Value *ByteOffset =
IndexScale == 1
? RowIndex
: Builder.CreateMul(RowIndex, Builder.getInt32(IndexScale));
uint64_t BaseWithinRow = GlobalOffsetVal % hlsl::CBufferRowSizeInBytes;
if (BaseWithinRow != 0)
ByteOffset =
Builder.CreateAdd(ByteOffset, Builder.getInt32(BaseWithinRow));
Value *RowSize = Builder.getInt32(hlsl::CBufferRowSizeInBytes);
KnownBits WithinRow =
computeCBufferOffsetKnownBits(ByteOffset, DL, LI) &
KnownBits::makeConstant(APInt(32, hlsl::CBufferRowSizeInBytes - 1));
if (!WithinRow.isConstant()) {
LI->replaceAllUsesWith(createDynamicCBufferLoad(
II, LI, Intrin, CurrentRow, ByteOffset, WithinRow));
return;
}
RowIndex = Builder.CreateUDiv(ByteOffset, RowSize, "",
WithinRow.getConstant().isZero());
CurrentIndex = WithinRow.getConstant().getZExtValue() / Intrin.EltSize;
} else if (IndexScale > hlsl::CBufferRowSizeInBytes) {
assert(IndexScale % hlsl::CBufferRowSizeInBytes == 0 &&
"CBuffer GEP index is not row aligned");
RowIndex = Builder.CreateMul(
RowIndex,
ConstantInt::get(Builder.getInt32Ty(),
IndexScale / hlsl::CBufferRowSizeInBytes));
}
if (auto *BaseRow = dyn_cast<ConstantInt>(CurrentRow);
BaseRow && BaseRow->isZero())
CurrentRow = RowIndex;
else
CurrentRow = Builder.CreateAdd(CurrentRow, RowIndex);
}
}
auto *CBufLoad = Builder.CreateIntrinsic(
Intrin.RetTy, Intrin.IID, {Handle, CurrentRow}, nullptr, Name + ".load");
auto *Elt =
Builder.CreateExtractValue(CBufLoad, {CurrentIndex++}, Name + ".extract");
// At this point we've loaded the first scalar of our result, but our original
// type may have been a vector.
unsigned int Remaining =
((DL.getTypeSizeInBits(Ty) / 8) / Intrin.EltSize) - 1;
if (Remaining == 0) {
// We only have a single element, so we're done.
Value *Result = Elt;
// However, if we loaded a <1 x T>, then we need to adjust the type.
if (auto *VT = dyn_cast<FixedVectorType>(Ty)) {
assert(VT->getNumElements() == 1 && "Can't have multiple elements here");
Result = Builder.CreateInsertElement(PoisonValue::get(VT), Result,
Builder.getInt32(0), Name);
}
LI->replaceAllUsesWith(Result);
return;
}
// Walk each element and extract it, wrapping to new rows as needed.
SmallVector<Value *> Extracts{Elt};
while (Remaining--) {
CurrentIndex %= Intrin.NumElts;
if (CurrentIndex == 0) {
CurrentRow = Builder.CreateAdd(CurrentRow,
ConstantInt::get(Builder.getInt32Ty(), 1));
CBufLoad = Builder.CreateIntrinsic(Intrin.RetTy, Intrin.IID,
{Handle, CurrentRow}, nullptr,
Name + ".load");
}
Extracts.push_back(Builder.CreateExtractValue(CBufLoad, {CurrentIndex++},
Name + ".extract"));
}
// Finally, we build up the original loaded value.
Value *Result = PoisonValue::get(Ty);
for (int I = 0, E = Extracts.size(); I < E; ++I)
Result = Builder.CreateInsertElement(
Result, Extracts[I], Builder.getInt32(I), Name + formatv(".upto{}", I));
LI->replaceAllUsesWith(Result);
}
static void createLoadIntrinsic(IntrinsicInst *II, LoadInst *LI,
dxil::ResourceTypeInfo &RTI) {
switch (RTI.getResourceKind()) {
case dxil::ResourceKind::TypedBuffer:
return createTypedBufferLoad(II, LI, RTI);
case dxil::ResourceKind::RawBuffer:
case dxil::ResourceKind::StructuredBuffer:
return createRawLoads(II, LI, RTI);
case dxil::ResourceKind::CBuffer:
return createCBufferLoad(II, LI, RTI);
case dxil::ResourceKind::Texture1D:
case dxil::ResourceKind::Texture2D:
case dxil::ResourceKind::Texture2DMS:
case dxil::ResourceKind::Texture3D:
case dxil::ResourceKind::TextureCube:
case dxil::ResourceKind::Texture1DArray:
case dxil::ResourceKind::Texture2DArray:
case dxil::ResourceKind::Texture2DMSArray:
case dxil::ResourceKind::TextureCubeArray:
return createTextureLoad(II, LI, RTI);
case dxil::ResourceKind::FeedbackTexture2D:
case dxil::ResourceKind::FeedbackTexture2DArray:
case dxil::ResourceKind::TBuffer:
reportFatalUsageError("Load not yet implemented for resource type");
return;
case dxil::ResourceKind::Sampler:
case dxil::ResourceKind::RTAccelerationStructure:
case dxil::ResourceKind::Invalid:
case dxil::ResourceKind::NumEntries:
llvm_unreachable("Invalid resource kind for load");
}
llvm_unreachable("Unhandled case in switch");
}
static Instruction *getHandleOperand(Instruction *AI) {
if (auto *LI = dyn_cast<LoadInst>(AI))
return dyn_cast<Instruction>(LI->getPointerOperand());
if (auto *SI = dyn_cast<StoreInst>(AI))
return dyn_cast<Instruction>(SI->getPointerOperand());
if (auto *RMWI = dyn_cast<AtomicRMWInst>(AI))
return dyn_cast<Instruction>(RMWI->getPointerOperand());
if (auto *CXI = dyn_cast<AtomicCmpXchgInst>(AI))
return dyn_cast<Instruction>(CXI->getPointerOperand());
if (auto *II = dyn_cast<IntrinsicInst>(AI))
if (II->getIntrinsicID() == Intrinsic::dx_resource_updatecounter)
return dyn_cast<Instruction>(II->getArgOperand(0));
return nullptr;
}
static Instruction *getHandleRoot(Instruction *I) {
while (auto *GEP = dyn_cast<GetElementPtrInst>(I)) {
auto *PointerI = dyn_cast<Instruction>(GEP->getPointerOperand());
if (!PointerI)
break;
I = PointerI;
}
return I;
}
static const std::array<Intrinsic::ID, 2> HandleIntrins = {
Intrinsic::dx_resource_handlefrombinding,
Intrinsic::dx_resource_handlefromimplicitbinding,
};
static SmallVector<IntrinsicInst *> collectUsedHandles(Value *Ptr) {
SmallVector<Value *> Worklist = {Ptr};
SmallVector<IntrinsicInst *> Handles;
SmallPtrSet<Value *, 4> VisitedPhis;
while (!Worklist.empty()) {
Value *X = Worklist.pop_back_val();
if (!X->getType()->isPointerTy() && !X->getType()->isTargetExtTy())
return {}; // Early exit on store/load into non-resource
if (auto *Phi = dyn_cast<PHINode>(X)) {
if (VisitedPhis.contains(X))
continue;
for (Use &V : Phi->incoming_values())
Worklist.push_back(V.get());
VisitedPhis.insert(Phi);
} else if (auto *Select = dyn_cast<SelectInst>(X)) {
Worklist.push_back(Select->getTrueValue());
Worklist.push_back(Select->getFalseValue());
} else if (auto *GEP = dyn_cast<GetElementPtrInst>(X)) {
Worklist.push_back(GEP->getPointerOperand());
} else if (auto *II = dyn_cast<IntrinsicInst>(X)) {
Intrinsic::ID IID = II->getIntrinsicID();
if (IID == Intrinsic::dx_resource_getpointer ||
IID == Intrinsic::dx_resource_getbasepointer)
Worklist.push_back(II->getArgOperand(/*Handle=*/0));
if (llvm::is_contained(HandleIntrins, IID))
Handles.push_back(II);
}
}
return Handles;
}
static hlsl::Binding getHandleIntrinsicBinding(IntrinsicInst *Handle,
DXILResourceTypeMap &DRTM) {
assert(llvm::is_contained(HandleIntrins, Handle->getIntrinsicID()) &&
"Only expects a Handle as determined from collectUsedHandles.");
auto *HandleTy = cast<TargetExtType>(Handle->getType());
dxil::ResourceClass Class = DRTM[HandleTy].getResourceClass();
uint32_t Space = cast<ConstantInt>(Handle->getArgOperand(0))->getZExtValue();
uint32_t LowerBound =
cast<ConstantInt>(Handle->getArgOperand(1))->getZExtValue();
uint32_t Size = cast<ConstantInt>(Handle->getArgOperand(2))->getZExtValue();
uint32_t UpperBound = Size == UINT32_MAX ? UINT32_MAX : LowerBound + Size - 1;
return hlsl::Binding(Class, Space, LowerBound, UpperBound, nullptr);
}
namespace {
/// Helper for propagating the current handle and ptr indices.
struct AccessIndices {
Value *GetPtrIdx;
Value *HandleIdx;
Value *OffsetIdx;
bool hasGetPtrIdx() { return GetPtrIdx != nullptr; }
bool hasHandleIdx() { return HandleIdx != nullptr; }
bool hasOffsetIdx() { return OffsetIdx != nullptr; }
};
/// AccessIndices cached per phi. The values may be placeholder phis that are
/// later folded away (via replaceAllUsesWith) and freed, so track them with
/// value handles rather than raw pointers.
struct TrackedAccessIndices {
WeakTrackingVH GetPtrIdx;
WeakTrackingVH HandleIdx;
WeakTrackingVH OffsetIdx;
TrackedAccessIndices() = default;
TrackedAccessIndices(const AccessIndices &AI)
: GetPtrIdx(AI.GetPtrIdx), HandleIdx(AI.HandleIdx),
OffsetIdx(AI.OffsetIdx) {}
operator AccessIndices() const { return {GetPtrIdx, HandleIdx, OffsetIdx}; }
};
using PhiAccessIndicesMap = SmallDenseMap<PHINode *, TrackedAccessIndices>;
} // namespace
// Compute the total byte offset described by a GEP as a single i32 value,
// handling both constant and variable indices.
static Value *accumulateGEPOffset(GetElementPtrInst *GEP,
IRBuilder<> &Builder) {
const DataLayout &DL = GEP->getDataLayout();
unsigned BitWidth = DL.getIndexTypeSizeInBits(GEP->getType());
SmallMapVector<Value *, APInt, 4> VariableOffsets;
APInt ConstantOffset(BitWidth, 0);
bool Success =
GEP->collectOffset(DL, BitWidth, VariableOffsets, ConstantOffset);
assert(Success && "Unhandled GEP structure for resource access");
(void)Success;
Type *I32 = Builder.getInt32Ty();
Value *Offset = ConstantInt::get(I32, ConstantOffset.getSExtValue());
for (auto &[V, Scale] : VariableOffsets) {
Value *Index = Builder.CreateSExtOrTrunc(V, I32);
if (!Scale.isOne())
Index =
Builder.CreateMul(Index, ConstantInt::get(I32, Scale.getSExtValue()));
Offset = Builder.CreateAdd(Offset, Index);
}
return Offset;
}
// getAccessIndices traverses up the control flow that a ptr came from and
// propagates back the indicies used to access the resource (AccessIndices):
//
// - GetPtrIdx is the index of dx.resource.getpointer
// - HandleIdx is the index of dx.resource.handlefrom.*
// - OffsetIdx is the accumulated byte offset of any GEPs in the ptr chain
static AccessIndices
getAccessIndices(Instruction *I, SmallSetVector<Instruction *, 16> &DeadInsts,
PhiAccessIndicesMap &VisitedPhis) {
if (auto *II = dyn_cast<IntrinsicInst>(I)) {
if (llvm::is_contained(HandleIntrins, II->getIntrinsicID())) {
DeadInsts.insert(II);
return {nullptr, II->getArgOperand(/*Index=*/3), nullptr};
}
Intrinsic::ID IID = II->getIntrinsicID();
if (IID == Intrinsic::dx_resource_getpointer ||
IID == Intrinsic::dx_resource_getbasepointer) {
auto *V = dyn_cast<Instruction>(II->getArgOperand(/*Handle=*/0));
auto AccessIdx = getAccessIndices(V, DeadInsts, VisitedPhis);
assert(!AccessIdx.hasGetPtrIdx() &&
"Encountered multiple dx.resource.getpointers in ptr chain?");
IRBuilder<> Builder(II);
AccessIdx.GetPtrIdx = ConstantInt::get(Builder.getInt32Ty(), 0);
if (IID == Intrinsic::dx_resource_getpointer)
AccessIdx.GetPtrIdx = II->getArgOperand(1);
DeadInsts.insert(II);
return AccessIdx;
}
}
if (auto *GEP = dyn_cast<GetElementPtrInst>(I)) {
auto *V = dyn_cast<Instruction>(GEP->getPointerOperand());
auto AccessIdx = getAccessIndices(V, DeadInsts, VisitedPhis);
IRBuilder<> Builder(GEP);
Value *GEPOffset = accumulateGEPOffset(GEP, Builder);
AccessIdx.OffsetIdx =
AccessIdx.hasOffsetIdx()
? Builder.CreateAdd(AccessIdx.OffsetIdx, GEPOffset)
: GEPOffset;
DeadInsts.insert(GEP);
return AccessIdx;
}
if (auto *Phi = dyn_cast<PHINode>(I)) {
// If we're already building indices for this phi, return a ref to the phi
if (auto It = VisitedPhis.find(Phi); It != VisitedPhis.end())
return It->second;
unsigned NumEdges = Phi->getNumIncomingValues();
assert(NumEdges != 0 && "Malformed Phi Node");
IRBuilder<> Builder(Phi);
std::unique_ptr<PHINode> GetPtrPhi(
PHINode::Create(Builder.getInt32Ty(), NumEdges));
std::unique_ptr<PHINode> HandlePhi(
PHINode::Create(Builder.getInt32Ty(), NumEdges));
std::unique_ptr<PHINode> OffsetPhi(
PHINode::Create(Builder.getInt32Ty(), NumEdges));
bool HasOffset = false;
// Register a ref to this phi for a recursive phi. If a placeholder phi
// below folds to a single value, replaceAllUsesWith updates any cached
// entries that captured it.
if (Phi->getType()->isTargetExtTy())
VisitedPhis[Phi] = AccessIndices{/*GetPtrIdx=*/nullptr, HandlePhi.get(),
/*OffsetIdx=*/nullptr};
else if (Phi->getType()->isPointerTy())
VisitedPhis[Phi] =
AccessIndices{GetPtrPhi.get(), HandlePhi.get(), OffsetPhi.get()};
for (unsigned Idx = 0; Idx < NumEdges; Idx++) {
auto *BB = Phi->getIncomingBlock(Idx);
auto *V = dyn_cast<Instruction>(Phi->getIncomingValue(Idx));
auto AccessIdx = getAccessIndices(V, DeadInsts, VisitedPhis);
if (AccessIdx.hasGetPtrIdx())
GetPtrPhi->addIncoming(AccessIdx.GetPtrIdx, BB);
HandlePhi->addIncoming(AccessIdx.HandleIdx, BB);
if (AccessIdx.hasOffsetIdx()) {
OffsetPhi->addIncoming(AccessIdx.OffsetIdx, BB);
HasOffset = true;
} else {
OffsetPhi->addIncoming(ConstantInt::get(Builder.getInt32Ty(), 0), BB);
}
}
Value *GetPtrIdx;
if (GetPtrPhi->getNumIncomingValues() == 0) {
GetPtrIdx = nullptr;
} else if (Value *ConstantGetPtr = GetPtrPhi->hasConstantValue()) {
GetPtrIdx = ConstantGetPtr;
GetPtrPhi->replaceAllUsesWith(GetPtrIdx);
} else {
GetPtrIdx = GetPtrPhi.release();
Builder.Insert(GetPtrIdx);
}
Value *HandleIdx;
if (Value *ConstantHandle = HandlePhi->hasConstantValue()) {
HandleIdx = ConstantHandle;
HandlePhi->replaceAllUsesWith(HandleIdx);
} else {
HandleIdx = HandlePhi.release();
Builder.Insert(HandleIdx);
}
Value *OffsetIdx = nullptr;
if (HasOffset) {
if (Value *ConstantIdx = OffsetPhi->hasConstantValue()) {
OffsetIdx = ConstantIdx;
OffsetPhi->replaceAllUsesWith(OffsetIdx);
} else {
OffsetIdx = OffsetPhi.release();
Builder.Insert(OffsetIdx);
}
}
// Replace the placeholder entry with the final indices.
AccessIndices Result = {GetPtrIdx, HandleIdx, OffsetIdx};
VisitedPhis[Phi] = Result;
DeadInsts.insert(Phi);
return Result;
}
if (auto *Select = dyn_cast<SelectInst>(I)) {
auto *TrueV = dyn_cast<Instruction>(Select->getTrueValue());
auto TrueAccessIdx = getAccessIndices(TrueV, DeadInsts, VisitedPhis);
auto *FalseV = dyn_cast<Instruction>(Select->getFalseValue());
auto FalseAccessIdx = getAccessIndices(FalseV, DeadInsts, VisitedPhis);
IRBuilder<> Builder(Select);
Value *GetPtrSelect = nullptr;
Value *OffsetSelect = nullptr;
if (TrueAccessIdx.hasGetPtrIdx() && FalseAccessIdx.hasGetPtrIdx())
GetPtrSelect =
Builder.CreateSelect(Select->getCondition(), TrueAccessIdx.GetPtrIdx,
FalseAccessIdx.GetPtrIdx, "", Select);
auto *HandleSelect =
Builder.CreateSelect(Select->getCondition(), TrueAccessIdx.HandleIdx,
FalseAccessIdx.HandleIdx, "", Select);
if (TrueAccessIdx.hasOffsetIdx() || FalseAccessIdx.hasOffsetIdx()) {
Value *Zero = ConstantInt::get(Builder.getInt32Ty(), 0);
Value *TrueOffset =
TrueAccessIdx.hasOffsetIdx() ? TrueAccessIdx.OffsetIdx : Zero;
Value *FalseOffset =
FalseAccessIdx.hasOffsetIdx() ? FalseAccessIdx.OffsetIdx : Zero;
OffsetSelect = Builder.CreateSelect(Select->getCondition(), TrueOffset,
FalseOffset, "", Select);
}
DeadInsts.insert(Select);
return {GetPtrSelect, HandleSelect, OffsetSelect};
}
llvm_unreachable("collectUsedHandles should assure this does not occur");
}
static void
replaceHandleWithIndices(Instruction *Ptr, IntrinsicInst *OldHandle,
SmallSetVector<Instruction *, 16> &DeadInsts,
PhiAccessIndicesMap &VisitedPhis) {
auto AccessIdx = getAccessIndices(Ptr, DeadInsts, VisitedPhis);
assert(AccessIdx.hasHandleIdx() &&
"Couldn't retrieve handle index. This is guaranteed by "
"getAccessIndices");
IRBuilder<> Builder(Ptr);
if (isa<PHINode>(Ptr))
Builder.SetInsertPoint(Ptr->getParent()->getFirstNonPHIIt());
IntrinsicInst *Handle = cast<IntrinsicInst>(OldHandle->clone());
Handle->setArgOperand(/*Index=*/3, AccessIdx.HandleIdx);
Builder.Insert(Handle);
if (Ptr->getType()->isPointerTy()) {
assert(AccessIdx.hasGetPtrIdx() &&
"Couldn't retrieve getpointer index. This is guaranteed by "
"getAccessIndices");
auto *GetPtr = Builder.CreateIntrinsic(Ptr->getType(),
Intrinsic::dx_resource_getpointer,
{Handle, AccessIdx.GetPtrIdx});
// Reapply any byte offset that came from GEPs in the original ptr chain so
// that later access lowering still sees it.
Value *Result = GetPtr;
if (AccessIdx.hasOffsetIdx())
Result = Builder.CreatePtrAdd(GetPtr, AccessIdx.OffsetIdx);
Ptr->replaceAllUsesWith(Result);
} else {
assert(Ptr->getType()->isTargetExtTy() && !AccessIdx.hasGetPtrIdx() &&
"Unexpected resource access operand type");
Ptr->replaceAllUsesWith(Handle);
}
DeadInsts.insert(Ptr);
}
// Try to legalize dx.resource.handlefrom.*.binding and dx.resource.getpointer
// calls with their respective index values and propagate the index values to
// be used at resource access.
//
// If it can't be transformed to be legal then:
//
// Reports an error if a resource access is not guaranteed into a unique global
// resource.
//
// Returns true if any changes are made.
static bool legalizeResourceHandles(Function &F, DXILResourceTypeMap &DRTM) {
SmallSetVector<Instruction *, 16> DeadInsts;
PhiAccessIndicesMap VisitedPhis;
for (BasicBlock &BB : make_early_inc_range(F)) {
for (Instruction &I : BB) {
if (auto *HandleOp = getHandleOperand(&I)) {
SmallVector<IntrinsicInst *> Handles = collectUsedHandles(HandleOp);
unsigned NumHandles = Handles.size();
if (NumHandles <= 1)
continue; // Legal, no-replacement required
bool SameGlobalBinding = true;
hlsl::Binding B = getHandleIntrinsicBinding(Handles[0], DRTM);
for (unsigned Idx = 1; Idx < NumHandles; Idx++)
SameGlobalBinding &=
(B == getHandleIntrinsicBinding(Handles[Idx], DRTM));
if (!SameGlobalBinding)
diagnoseNonUniqueResourceAccess(&I, Handles);
replaceHandleWithIndices(getHandleRoot(HandleOp), Handles[0], DeadInsts,
VisitedPhis);
}
}
}
bool MadeChanges = false;
// Set up the phis to track if they are erased below
SmallVector<WeakTrackingVH> ResourcePhis;
for (const auto &HandleToIndex : VisitedPhis)
ResourcePhis.push_back(HandleToIndex.first);
for (auto *I : llvm::reverse(DeadInsts))
if (I->hasNUses(0)) { // Handle can still be used outside of replaced path
I->eraseFromParent();
MadeChanges = true;
}
// Any remaining phi nodes are now looped with another phi node and have no
// other uses
for (WeakTrackingVH &VH : ResourcePhis)
if (VH) // True if not removed above or already in this loop
MadeChanges |= RecursivelyDeleteDeadPHINode(cast<PHINode>(VH));
return MadeChanges;
}
static void replaceAccess(IntrinsicInst *II, dxil::ResourceTypeInfo &RTI) {
SmallVector<User *> Worklist;
for (User *U : II->users())
Worklist.push_back(U);
SmallVector<Instruction *> DeadInsts;
while (!Worklist.empty()) {
User *U = Worklist.back();
Worklist.pop_back();
if (auto *GEP = dyn_cast<GetElementPtrInst>(U)) {
for (User *U : GEP->users())
Worklist.push_back(U);
DeadInsts.push_back(GEP);
} else if (auto *SI = dyn_cast<StoreInst>(U)) {
assert(SI->getValueOperand() != II && "Pointer escaped!");
createStoreIntrinsic(II, SI, RTI);
DeadInsts.push_back(SI);
} else if (auto *LI = dyn_cast<LoadInst>(U)) {
createLoadIntrinsic(II, LI, RTI);
DeadInsts.push_back(LI);
} else if (auto *AI = dyn_cast<AtomicRMWInst>(U)) {
createAtomicBinOpIntrinsic(II, AI, RTI);
DeadInsts.push_back(AI);
} else if (auto *CXI = dyn_cast<AtomicCmpXchgInst>(U)) {
createAtomicCompareExchangeIntrinsic(II, CXI, RTI);
DeadInsts.push_back(CXI);
} else
llvm_unreachable("Unhandled instruction - pointer escaped?");
}
// Traverse the now-dead instructions in RPO and remove them.
for (Instruction *Dead : llvm::reverse(DeadInsts))
Dead->eraseFromParent();
II->eraseFromParent();
}
static bool transformResourcePointers(Function &F, DXILResourceTypeMap &DRTM) {
SmallVector<std::pair<IntrinsicInst *, dxil::ResourceTypeInfo>> Resources;
for (BasicBlock &BB : make_early_inc_range(F))
for (Instruction &I : BB)
if (auto *II = dyn_cast<IntrinsicInst>(&I))
if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer ||
II->getIntrinsicID() == Intrinsic::dx_resource_getbasepointer) {
auto *HandleTy = cast<TargetExtType>(II->getArgOperand(0)->getType());
assert(
(DRTM[HandleTy].isCBuffer() ||
II->getIntrinsicID() != Intrinsic::dx_resource_getbasepointer) &&
"dx_resource_getbasepointer should only be used by cbuffers");
Resources.emplace_back(II, DRTM[HandleTy]);
}
for (auto &[II, RI] : Resources)
replaceAccess(II, RI);
return !Resources.empty();
}
PreservedAnalyses DXILResourceAccess::run(Function &F,
FunctionAnalysisManager &FAM) {
auto &MAMProxy = FAM.getResult<ModuleAnalysisManagerFunctionProxy>(F);
DXILResourceTypeMap *DRTM =
MAMProxy.getCachedResult<DXILResourceTypeAnalysis>(*F.getParent());
assert(DRTM && "DXILResourceTypeAnalysis must be available");
bool MadeHandleChanges = legalizeResourceHandles(F, *DRTM);
bool MadeResourceChanges = transformResourcePointers(F, *DRTM);
if (!(MadeHandleChanges || MadeResourceChanges))
return PreservedAnalyses::all();
PreservedAnalyses PA;
PA.preserve<DXILResourceTypeAnalysis>();
return PA;
}
namespace {
class DXILResourceAccessLegacy : public FunctionPass {
public:
bool runOnFunction(Function &F) override {
DXILResourceTypeMap &DRTM =
getAnalysis<DXILResourceTypeWrapperPass>().getResourceTypeMap();
bool MadeHandleChanges = legalizeResourceHandles(F, DRTM);
bool MadeResourceChanges = transformResourcePointers(F, DRTM);
return MadeHandleChanges || MadeResourceChanges;
}
StringRef getPassName() const override { return "DXIL Resource Access"; }
DXILResourceAccessLegacy() : FunctionPass(ID) {}
static char ID; // Pass identification.
void getAnalysisUsage(llvm::AnalysisUsage &AU) const override {
AU.addRequired<DXILResourceTypeWrapperPass>();
}
};
char DXILResourceAccessLegacy::ID = 0;
} // end anonymous namespace
INITIALIZE_PASS_BEGIN(DXILResourceAccessLegacy, DEBUG_TYPE,
"DXIL Resource Access", false, false)
INITIALIZE_PASS_DEPENDENCY(DXILResourceTypeWrapperPass)
INITIALIZE_PASS_END(DXILResourceAccessLegacy, DEBUG_TYPE,
"DXIL Resource Access", false, false)
FunctionPass *llvm::createDXILResourceAccessLegacyPass() {
return new DXILResourceAccessLegacy();
}