Function to compute uniform blocks from DXIL unstructured control flow

New files dxil_controlflow.{cpp,h}
This commit is contained in:
Jake Turner
2024-11-15 11:59:01 +00:00
parent 59cc12ff3f
commit f11db57d68
4 changed files with 420 additions and 0 deletions
@@ -0,0 +1,383 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2024 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include <map>
#include <set>
#include "dxil_controlflow.h"
/*
Inputs are links of blocks : from -> to (can be forwards or backwards links)
Output is a list of uniform control flow blocks which all possible flows go through (not diverged)
The algorithm is:
1. Setup
* Compute all possible known blocks.
* For each block generate a list of "to" blocks from the input links
* Any block without links in the input are set to link to the end sentinel (PATH_END)
2. Generate all possible paths
* Paths can terminate at the end block (PATH_END)
* Paths can also terminate at a block before the end, if that block has had all its possible paths
already computed
3. Find Uniform Blocks
* Generate a list of path indexes for each block in the paths
* uniform block is defined by any block which appears in all paths
* all paths includes walking any paths linked at the end node of the path being walked
*/
namespace DXIL
{
struct ControlFlow
{
public:
ControlFlow(const rdcarray<rdcpair<uint32_t, uint32_t>> &links);
void FindUniformBlocks(rdcarray<uint32_t> &uniformBlocks);
private:
typedef rdcarray<uint32_t> BlockPath;
bool TraceBlockFlow(const uint32_t from, BlockPath &path);
bool BlockInPath(uint32_t block, uint32_t pathIdx, uint32_t startIdx);
const uint32_t PATH_END = ~0U;
std::set<uint32_t> m_Blocks;
std::map<uint32_t, BlockPath> m_BlockLinks;
std::map<uint32_t, rdcarray<uint32_t>> m_BlockPathLinks;
std::set<uint32_t> m_TracedBlocks;
std::set<uint32_t> m_CheckedPaths;
rdcarray<BlockPath> m_Paths;
};
ControlFlow::ControlFlow(const rdcarray<rdcpair<uint32_t, uint32_t>> &links)
{
// 1. Setup
// Compute all possible known blocks
for(const auto &link : links)
{
uint32_t from = link.first;
uint32_t to = link.second;
m_Blocks.insert(from);
m_Blocks.insert(to);
}
// For each block a list of "to" blocks
for(const auto &link : links)
{
uint32_t from = link.first;
uint32_t to = link.second;
m_BlockLinks[from].push_back(to);
}
// Any block without links in the input are set to link to the end sentinel (PATH_END)
for(uint32_t b : m_Blocks)
{
if(m_BlockLinks.count(b) == 0)
m_BlockLinks[b].push_back(PATH_END);
}
// 2. Generate all possible paths
// Paths can terminate at the end block (PATH_END)
// Paths can also terminate at a block before the end, if that block has had all its possible paths already computed
for(const auto &it : m_BlockLinks)
{
uint32_t from = it.first;
if(m_TracedBlocks.count(from) != 0)
continue;
BlockPath path;
path.push_back(from);
TraceBlockFlow(from, path);
}
}
bool ControlFlow::TraceBlockFlow(const uint32_t from, BlockPath &path)
{
if(m_BlockLinks.count(from) == 0)
{
m_Paths.push_back(path);
return true;
}
if(m_TracedBlocks.count(from) != 0)
{
m_Paths.push_back(path);
return true;
}
m_TracedBlocks.insert(from);
rdcarray<uint32_t> newPath = path;
const BlockPath &gotos = m_BlockLinks.at(from);
for(uint32_t to : gotos)
{
newPath.push_back(to);
if(TraceBlockFlow(to, newPath))
newPath = path;
}
return true;
}
bool ControlFlow::BlockInPath(uint32_t block, uint32_t pathIdx, uint32_t startIdx)
{
const rdcarray<uint32_t> &path = m_Paths[pathIdx];
if(path.size() == 0)
return false;
// Check the current path
for(uint32_t i = startIdx; i < path.size(); ++i)
{
if(block == path[i])
return true;
}
m_CheckedPaths.insert(pathIdx);
uint32_t endNode = path[path.size() - 1];
if(endNode == PATH_END)
return false;
// Check any paths linked to by the end node of the current path
const rdcarray<uint32_t> &childPathsToCheck = m_BlockPathLinks[endNode];
for(uint32_t childPathIdx : childPathsToCheck)
{
if(m_CheckedPaths.count(childPathIdx) != 0)
continue;
m_CheckedPaths.insert(childPathIdx);
const rdcarray<uint32_t> &childPath = m_Paths[childPathIdx];
uint32_t childPartStartIdx = ~0U;
for(childPartStartIdx = 0; childPartStartIdx < childPath.size(); ++childPartStartIdx)
{
if(childPath[childPartStartIdx] == endNode)
break;
}
if(!BlockInPath(block, childPathIdx, childPartStartIdx))
return false;
}
return true;
}
void ControlFlow::FindUniformBlocks(rdcarray<uint32_t> &uniformBlocks)
{
// 3. Find Uniform Blocks
for(uint32_t b : m_Blocks)
m_BlockPathLinks[b].clear();
// Generate a list of path indexes for each block in the paths
for(uint32_t pathIdx = 0; pathIdx < m_Paths.size(); ++pathIdx)
{
for(uint32_t block : m_Paths[pathIdx])
{
if(block == PATH_END)
break;
m_BlockPathLinks[block].push_back(pathIdx);
}
}
uniformBlocks.clear();
// A uniform block is defined by any block which appears in all paths
// all paths includes walking any paths linked at the end node of the path being walked
for(uint32_t block : m_Blocks)
{
bool uniform = true;
for(uint32_t pathIdx = 0; pathIdx < m_Paths.size(); ++pathIdx)
{
m_CheckedPaths.clear();
// BlockInPath will also check all paths linked to from the end node of the path
if(!BlockInPath(block, pathIdx, 0))
{
uniform = false;
break;
}
}
if(uniform)
uniformBlocks.push_back(block);
}
}
void FindUniformBlocks(const rdcarray<BlockLink> &links, rdcarray<uint32_t> &uniformBlocks)
{
ControlFlow controlFlow(links);
controlFlow.FindUniformBlocks(uniformBlocks);
}
}; // namespace DXIL
#if ENABLED(ENABLE_UNIT_TESTS)
#include <limits>
#include "catch/catch.hpp"
using namespace DXIL;
TEST_CASE("DXIL Control Flow", "[dxil]")
{
SECTION("FindUniformBlocks")
{
rdcarray<uint32_t> outputs;
{
// Degenerate case
rdcarray<BlockLink> inputs;
DXIL::FindUniformBlocks(inputs, outputs);
REQUIRE(0 == outputs.count());
}
{
// Only uniform flow is the start and end
// 0 -> 1
rdcarray<BlockLink> inputs;
inputs.push_back({0, 1});
DXIL::FindUniformBlocks(inputs, outputs);
REQUIRE(2 == outputs.count());
REQUIRE(outputs.contains(0U));
REQUIRE(outputs.contains(1U));
}
{
// Single uniform flow between start and end
// 0 -> 1 -> 3
// 0 -> 2 -> 3
// 3 -> 4
rdcarray<BlockLink> inputs;
inputs.push_back({0, 1});
inputs.push_back({1, 3});
inputs.push_back({0, 2});
inputs.push_back({2, 3});
inputs.push_back({3, 4});
DXIL::FindUniformBlocks(inputs, outputs);
REQUIRE(3 == outputs.count());
REQUIRE(outputs.contains(0U));
REQUIRE(outputs.contains(3U));
REQUIRE(outputs.contains(4U));
}
{
// Finite Loop (3 -> 4 -> 5 -> 3)
// 0 -> 1 -> 3
// 0 -> 2 -> 3
// 3 -> 4 -> 5
// 4 -> 6
// 5 -> 3
// 5 -> 6
rdcarray<BlockLink> inputs;
inputs.push_back({0, 1});
inputs.push_back({1, 3});
inputs.push_back({0, 2});
inputs.push_back({2, 3});
inputs.push_back({3, 4});
inputs.push_back({4, 5});
inputs.push_back({4, 6});
inputs.push_back({5, 3});
inputs.push_back({5, 6});
DXIL::FindUniformBlocks(inputs, outputs);
REQUIRE(4 == outputs.count());
REQUIRE(outputs.contains(0U));
REQUIRE(outputs.contains(3U));
REQUIRE(outputs.contains(4U));
REQUIRE(outputs.contains(6U));
}
{
// Infinite loop which never converges (3 -> 4 -> 3)
// 0 -> 1 -> 3
// 0 -> 2 -> 3
// 3 -> 4
// 4 -> 3
// 1 -> 6
// 2 -> 6
rdcarray<BlockLink> inputs;
inputs.push_back({0, 1});
inputs.push_back({1, 3});
inputs.push_back({0, 2});
inputs.push_back({2, 3});
inputs.push_back({3, 4});
inputs.push_back({4, 3});
inputs.push_back({1, 6});
inputs.push_back({2, 6});
DXIL::FindUniformBlocks(inputs, outputs);
REQUIRE(2 == outputs.count());
REQUIRE(outputs.contains(0U));
REQUIRE(outputs.contains(6U));
}
{
// Complex case with multiple loops
rdcarray<BlockLink> inputs;
inputs.push_back({0, 1});
inputs.push_back({0, 2});
inputs.push_back({2, 3});
inputs.push_back({1, 3});
inputs.push_back({3, 4});
inputs.push_back({4, 5});
inputs.push_back({3, 5});
inputs.push_back({5, 6});
inputs.push_back({9, 7});
inputs.push_back({6, 7});
inputs.push_back({7, 8});
inputs.push_back({7, 9});
inputs.push_back({9, 10});
inputs.push_back({10, 11});
inputs.push_back({8, 11});
inputs.push_back({5, 11});
inputs.push_back({11, 12});
inputs.push_back({15, 13});
inputs.push_back({12, 13});
inputs.push_back({13, 14});
inputs.push_back({13, 15});
inputs.push_back({15, 16});
inputs.push_back({16, 17});
inputs.push_back({14, 17});
inputs.push_back({11, 17});
inputs.push_back({17, 18});
inputs.push_back({18, 19});
inputs.push_back({17, 19});
inputs.push_back({19, 20});
inputs.push_back({20, 21});
inputs.push_back({19, 21});
inputs.push_back({21, 22});
inputs.push_back({22, 23});
inputs.push_back({22, 24});
inputs.push_back({24, 25});
inputs.push_back({25, 26});
inputs.push_back({24, 26});
inputs.push_back({23, 26});
inputs.push_back({21, 26});
DXIL::FindUniformBlocks(inputs, outputs);
REQUIRE(8 == outputs.count());
REQUIRE(outputs.contains(0U));
REQUIRE(outputs.contains(3U));
REQUIRE(outputs.contains(5U));
REQUIRE(outputs.contains(11U));
REQUIRE(outputs.contains(17U));
REQUIRE(outputs.contains(19U));
REQUIRE(outputs.contains(21U));
REQUIRE(outputs.contains(26U));
}
};
};
#endif // ENABLED(ENABLE_UNIT_TESTS)
@@ -0,0 +1,33 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2024 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
namespace DXIL
{
typedef rdcpair<uint32_t, uint32_t> BlockLink;
void FindUniformBlocks(const rdcarray<BlockLink> &links, rdcarray<uint32_t> &uniformBlocks);
}; // namespace DXIL
@@ -104,6 +104,7 @@
<ClCompile Include="dxil_bytecode.cpp" />
<ClCompile Include="dxil_bytecode_editor.cpp" />
<ClCompile Include="dxil_common.cpp" />
<ClCompile Include="dxil_controlflow.cpp" />
<ClCompile Include="dxil_debug.cpp" />
<ClCompile Include="dxil_debuginfo.cpp" />
<ClCompile Include="dxil_disassemble.cpp" />
@@ -120,6 +121,7 @@
<ClInclude Include="dxil_bytecode.h" />
<ClInclude Include="dxil_bytecode_editor.h" />
<ClInclude Include="dxil_common.h" />
<ClInclude Include="dxil_controlflow.h" />
<ClInclude Include="dxil_debug.h" />
<ClInclude Include="dxil_debuginfo.h" />
<ClInclude Include="dxil_metadata.h" />
@@ -15,6 +15,7 @@
<ClCompile Include="dxil_debug.cpp" />
<ClCompile Include="dxil_stringise.cpp" />
<ClCompile Include="dxil_metadata.cpp" />
<ClCompile Include="dxil_controlflow.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="precompiled.h">
@@ -31,6 +32,7 @@
<ClInclude Include="llvm_common.h" />
<ClInclude Include="dxil_debug.h" />
<ClInclude Include="dxil_metadata.h" />
<ClInclude Include="dxil_controlflow.h" />
</ItemGroup>
<ItemGroup>
<Filter Include="PCH">