-
Notifications
You must be signed in to change notification settings - Fork 14.8k
[HLSL] Add support to lookup a ResourceBindingInfo from its use #126556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ab1e78d
e7d1042
09b6373
2039b3b
f3e3b3f
cd70254
d2f17eb
2ac7284
c17776b
48a0201
010c535
2385cef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -770,6 +770,45 @@ void DXILBindingMap::print(raw_ostream &OS, DXILResourceTypeMap &DRTM, | |
} | ||
} | ||
|
||
SmallVector<dxil::ResourceBindingInfo> | ||
DXILBindingMap::findByUse(const Value *Key) const { | ||
if (const PHINode *Phi = dyn_cast<PHINode>(Key)) { | ||
SmallVector<dxil::ResourceBindingInfo> Children; | ||
for (const Value *V : Phi->operands()) { | ||
Children.append(findByUse(V)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without a set that tracks and skips already visited There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uhhh maybe, but I dont think so? Can you formulate an example? I've been trying myself for a bit and everything I come up with violates SSA. Visited lists are only relevant when cycles are possible and in order to introduce a cycle we need a CallInstr to reference something not yet defined and that's not allowed right?
|
||
} | ||
return Children; | ||
} | ||
|
||
const CallInst *CI = dyn_cast<CallInst>(Key); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does it impact the recursion if we check for CallInst before PHINode? I kind of like my base cases to be first, but I know thats not always possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Happy to test it but it looks like Which means the base case would earily exit before the PHINode code had a chance to run. Could maybe tuck it inside the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah the trivial reorder causes the PHINodes case to fail |
||
if (!CI) | ||
return {}; | ||
|
||
switch (CI->getIntrinsicID()) { | ||
// Found the create, return the binding | ||
case Intrinsic::dx_resource_handlefrombinding: { | ||
const auto *It = find(CI); | ||
assert(It != Infos.end() && "HandleFromBinding must be in resource map"); | ||
return {*It}; | ||
} | ||
default: | ||
break; | ||
} | ||
|
||
// Check if any of the parameters are the resource we are following. If so | ||
// keep searching. If none of them are return an empty list | ||
const Type *UseType = CI->getType(); | ||
SmallVector<dxil::ResourceBindingInfo> Children; | ||
for (const Value *V : CI->args()) { | ||
if (V->getType() != UseType) | ||
continue; | ||
|
||
Children.append(findByUse(V)); | ||
} | ||
|
||
return Children; | ||
} | ||
|
||
//===----------------------------------------------------------------------===// | ||
|
||
AnalysisKey DXILResourceTypeAnalysis::Key; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,283 @@ | ||
//===- llvm/unittests/Target/DirectX/PointerTypeAnalysisTests.cpp ---------===// | ||
// | ||
// 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 "DirectXTargetMachine.h" | ||
#include "llvm/Analysis/DXILResource.h" | ||
#include "llvm/AsmParser/Parser.h" | ||
#include "llvm/CodeGen/CommandFlags.h" | ||
#include "llvm/IR/Instructions.h" | ||
#include "llvm/IR/LLVMContext.h" | ||
#include "llvm/IR/Module.h" | ||
#include "llvm/IR/Type.h" | ||
#include "llvm/Passes/PassBuilder.h" | ||
#include "llvm/Support/Casting.h" | ||
#include "llvm/Support/SourceMgr.h" | ||
|
||
#include "gtest/gtest.h" | ||
|
||
using namespace llvm; | ||
using namespace llvm::dxil; | ||
|
||
namespace { | ||
class UniqueResourceFromUseTest : public testing::Test { | ||
protected: | ||
PassBuilder *PB; | ||
ModuleAnalysisManager *MAM; | ||
|
||
virtual void SetUp() { | ||
MAM = new ModuleAnalysisManager(); | ||
PB = new PassBuilder(); | ||
PB->registerModuleAnalyses(*MAM); | ||
MAM->registerPass([&] { return DXILResourceTypeAnalysis(); }); | ||
MAM->registerPass([&] { return DXILResourceBindingAnalysis(); }); | ||
} | ||
|
||
virtual void TearDown() { | ||
delete PB; | ||
delete MAM; | ||
} | ||
}; | ||
|
||
TEST_F(UniqueResourceFromUseTest, TestTrivialUse) { | ||
StringRef Assembly = R"( | ||
define void @main() { | ||
entry: | ||
%handle = call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 1, i32 2, i32 3, i32 4, i1 false) | ||
call void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) | ||
call void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) | ||
ret void | ||
} | ||
|
||
declare target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32, i32, i32, i32, i1) | ||
declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) | ||
)"; | ||
|
||
LLVMContext Context; | ||
SMDiagnostic Error; | ||
auto M = parseAssemblyString(Assembly, Error, Context); | ||
ASSERT_TRUE(M) << "Bad assembly?"; | ||
|
||
const DXILBindingMap &DBM = MAM->getResult<DXILResourceBindingAnalysis>(*M); | ||
for (const Function &F : M->functions()) { | ||
if (F.getName() != "a.func") { | ||
continue; | ||
} | ||
|
||
unsigned CalledResources = 0; | ||
|
||
for (const User *U : F.users()) { | ||
const CallInst *CI = cast<CallInst>(U); | ||
const Value *Handle = CI->getArgOperand(0); | ||
const auto Bindings = DBM.findByUse(Handle); | ||
ASSERT_EQ(Bindings.size(), 1u) | ||
<< "Handle should resolve into one resource"; | ||
|
||
auto Binding = Bindings[0].getBinding(); | ||
EXPECT_EQ(0u, Binding.RecordID); | ||
EXPECT_EQ(1u, Binding.Space); | ||
EXPECT_EQ(2u, Binding.LowerBound); | ||
EXPECT_EQ(3u, Binding.Size); | ||
|
||
CalledResources++; | ||
} | ||
|
||
EXPECT_EQ(2u, CalledResources) | ||
<< "Expected 2 resolved call to create resource"; | ||
} | ||
} | ||
|
||
TEST_F(UniqueResourceFromUseTest, TestIndirectUse) { | ||
StringRef Assembly = R"( | ||
define void @foo() { | ||
%handle = call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 1, i32 2, i32 3, i32 4, i1 false) | ||
%handle2 = call target("dx.RawBuffer", float, 1, 0) @ind.func(target("dx.RawBuffer", float, 1, 0) %handle) | ||
%handle3 = call target("dx.RawBuffer", float, 1, 0) @ind.func(target("dx.RawBuffer", float, 1, 0) %handle2) | ||
%handle4 = call target("dx.RawBuffer", float, 1, 0) @ind.func(target("dx.RawBuffer", float, 1, 0) %handle3) | ||
call void @a.func(target("dx.RawBuffer", float, 1, 0) %handle4) | ||
ret void | ||
} | ||
|
||
declare target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32, i32, i32, i32, i1) | ||
declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) | ||
declare target("dx.RawBuffer", float, 1, 0) @ind.func(target("dx.RawBuffer", float, 1, 0) %handle) | ||
)"; | ||
|
||
LLVMContext Context; | ||
SMDiagnostic Error; | ||
auto M = parseAssemblyString(Assembly, Error, Context); | ||
ASSERT_TRUE(M) << "Bad assembly?"; | ||
|
||
const DXILBindingMap &DBM = MAM->getResult<DXILResourceBindingAnalysis>(*M); | ||
for (const Function &F : M->functions()) { | ||
if (F.getName() != "a.func") { | ||
continue; | ||
} | ||
|
||
unsigned CalledResources = 0; | ||
|
||
for (const User *U : F.users()) { | ||
const CallInst *CI = cast<CallInst>(U); | ||
const Value *Handle = CI->getArgOperand(0); | ||
const auto Bindings = DBM.findByUse(Handle); | ||
ASSERT_EQ(Bindings.size(), 1u) | ||
<< "Handle should resolve into one resource"; | ||
|
||
auto Binding = Bindings[0].getBinding(); | ||
EXPECT_EQ(0u, Binding.RecordID); | ||
EXPECT_EQ(1u, Binding.Space); | ||
EXPECT_EQ(2u, Binding.LowerBound); | ||
EXPECT_EQ(3u, Binding.Size); | ||
|
||
CalledResources++; | ||
} | ||
|
||
EXPECT_EQ(1u, CalledResources) | ||
<< "Expected 1 resolved call to create resource"; | ||
} | ||
} | ||
|
||
TEST_F(UniqueResourceFromUseTest, TestAmbigousIndirectUse) { | ||
StringRef Assembly = R"( | ||
define void @foo() { | ||
%foo = call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 1, i32 1, i32 1, i32 1, i1 false) | ||
%bar = call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 2, i32 2, i32 2, i32 2, i1 false) | ||
%baz = call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 3, i32 3, i32 3, i32 3, i1 false) | ||
%bat = call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 4, i32 4, i32 4, i32 4, i1 false) | ||
%a = call target("dx.RawBuffer", float, 1, 0) @ind.func(target("dx.RawBuffer", float, 1, 0) %foo, target("dx.RawBuffer", float, 1, 0) %bar) | ||
%b = call target("dx.RawBuffer", float, 1, 0) @ind.func(target("dx.RawBuffer", float, 1, 0) %baz, target("dx.RawBuffer", float, 1, 0) %bat) | ||
%handle = call target("dx.RawBuffer", float, 1, 0) @ind.func(target("dx.RawBuffer", float, 1, 0) %a, target("dx.RawBuffer", float, 1, 0) %b) | ||
call void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) | ||
ret void | ||
} | ||
|
||
declare target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32, i32, i32, i32, i1) | ||
declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) | ||
declare target("dx.RawBuffer", float, 1, 0) @ind.func(target("dx.RawBuffer", float, 1, 0) %x, target("dx.RawBuffer", float, 1, 0) %y) | ||
)"; | ||
|
||
LLVMContext Context; | ||
SMDiagnostic Error; | ||
auto M = parseAssemblyString(Assembly, Error, Context); | ||
ASSERT_TRUE(M) << "Bad assembly?"; | ||
|
||
const DXILBindingMap &DBM = MAM->getResult<DXILResourceBindingAnalysis>(*M); | ||
for (const Function &F : M->functions()) { | ||
if (F.getName() != "a.func") { | ||
continue; | ||
} | ||
|
||
unsigned CalledResources = 0; | ||
|
||
for (const User *U : F.users()) { | ||
const CallInst *CI = cast<CallInst>(U); | ||
const Value *Handle = CI->getArgOperand(0); | ||
const auto Bindings = DBM.findByUse(Handle); | ||
ASSERT_EQ(Bindings.size(), 4u) | ||
<< "Handle should resolve into four resources"; | ||
|
||
auto Binding = Bindings[0].getBinding(); | ||
EXPECT_EQ(0u, Binding.RecordID); | ||
EXPECT_EQ(1u, Binding.Space); | ||
EXPECT_EQ(1u, Binding.LowerBound); | ||
EXPECT_EQ(1u, Binding.Size); | ||
|
||
Binding = Bindings[1].getBinding(); | ||
EXPECT_EQ(1u, Binding.RecordID); | ||
EXPECT_EQ(2u, Binding.Space); | ||
EXPECT_EQ(2u, Binding.LowerBound); | ||
EXPECT_EQ(2u, Binding.Size); | ||
|
||
Binding = Bindings[2].getBinding(); | ||
EXPECT_EQ(2u, Binding.RecordID); | ||
EXPECT_EQ(3u, Binding.Space); | ||
EXPECT_EQ(3u, Binding.LowerBound); | ||
EXPECT_EQ(3u, Binding.Size); | ||
|
||
Binding = Bindings[3].getBinding(); | ||
EXPECT_EQ(3u, Binding.RecordID); | ||
EXPECT_EQ(4u, Binding.Space); | ||
EXPECT_EQ(4u, Binding.LowerBound); | ||
EXPECT_EQ(4u, Binding.Size); | ||
|
||
CalledResources++; | ||
} | ||
|
||
EXPECT_EQ(1u, CalledResources) | ||
<< "Expected 1 resolved call to create resource"; | ||
} | ||
} | ||
|
||
TEST_F(UniqueResourceFromUseTest, TestConditionalUse) { | ||
StringRef Assembly = R"( | ||
define void @foo(i32 %n) { | ||
entry: | ||
%x = call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 1, i32 1, i32 1, i32 1, i1 false) | ||
%y = call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 4, i32 4, i32 4, i32 4, i1 false) | ||
%cond = icmp eq i32 %n, 0 | ||
br i1 %cond, label %bb.true, label %bb.false | ||
|
||
bb.true: | ||
%handle_t = call target("dx.RawBuffer", float, 1, 0) @ind.func(target("dx.RawBuffer", float, 1, 0) %x) | ||
br label %bb.exit | ||
|
||
bb.false: | ||
%handle_f = call target("dx.RawBuffer", float, 1, 0) @ind.func(target("dx.RawBuffer", float, 1, 0) %y) | ||
br label %bb.exit | ||
|
||
bb.exit: | ||
%handle = phi target("dx.RawBuffer", float, 1, 0) [ %handle_t, %bb.true ], [ %handle_f, %bb.false ] | ||
call void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) | ||
ret void | ||
} | ||
|
||
declare target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32, i32, i32, i32, i1) | ||
declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) | ||
declare target("dx.RawBuffer", float, 1, 0) @ind.func(target("dx.RawBuffer", float, 1, 0) %x) | ||
)"; | ||
|
||
LLVMContext Context; | ||
SMDiagnostic Error; | ||
auto M = parseAssemblyString(Assembly, Error, Context); | ||
ASSERT_TRUE(M) << "Bad assembly?"; | ||
|
||
const DXILBindingMap &DBM = MAM->getResult<DXILResourceBindingAnalysis>(*M); | ||
for (const Function &F : M->functions()) { | ||
if (F.getName() != "a.func") { | ||
continue; | ||
} | ||
|
||
unsigned CalledResources = 0; | ||
|
||
for (const User *U : F.users()) { | ||
const CallInst *CI = cast<CallInst>(U); | ||
const Value *Handle = CI->getArgOperand(0); | ||
const auto Bindings = DBM.findByUse(Handle); | ||
ASSERT_EQ(Bindings.size(), 2u) | ||
<< "Handle should resolve into four resources"; | ||
|
||
auto Binding = Bindings[0].getBinding(); | ||
EXPECT_EQ(0u, Binding.RecordID); | ||
EXPECT_EQ(1u, Binding.Space); | ||
EXPECT_EQ(1u, Binding.LowerBound); | ||
EXPECT_EQ(1u, Binding.Size); | ||
|
||
Binding = Bindings[1].getBinding(); | ||
EXPECT_EQ(1u, Binding.RecordID); | ||
EXPECT_EQ(4u, Binding.Space); | ||
EXPECT_EQ(4u, Binding.LowerBound); | ||
EXPECT_EQ(4u, Binding.Size); | ||
|
||
CalledResources++; | ||
} | ||
|
||
EXPECT_EQ(1u, CalledResources) | ||
<< "Expected 1 resolved call to create resource"; | ||
} | ||
} | ||
|
||
} // namespace |
Uh oh!
There was an error while loading. Please reload this page.