shader_ir: Corrections, documenting and asserting control_flow
This commit is contained in:
		
				
					committed by
					
						 FernandoS27
						FernandoS27
					
				
			
			
				
	
			
			
			
						parent
						
							d5533b440c
						
					
				
				
					commit
					01b21ee1e8
				
			| @@ -1,3 +1,6 @@ | |||||||
|  | // Copyright 2019 yuzu Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
| #include <list> | #include <list> | ||||||
| #include <map> | #include <map> | ||||||
| @@ -17,16 +20,18 @@ using Tegra::Shader::OpCode; | |||||||
|  |  | ||||||
| constexpr s32 unassigned_branch = -2; | constexpr s32 unassigned_branch = -2; | ||||||
|  |  | ||||||
|  | /*** | ||||||
|  |  * 'ControlStack' represents a static stack of control jumps such as SSY and PBK | ||||||
|  |  * stacks in Maxwell. | ||||||
|  |  ***/ | ||||||
| struct ControlStack { | struct ControlStack { | ||||||
|     std::array<u32, 20> stack; |     static constexpr std::size_t stack_fixed_size = 20; | ||||||
|  |     std::array<u32, stack_fixed_size> stack{}; | ||||||
|     u32 index{}; |     u32 index{}; | ||||||
|  |  | ||||||
|     ControlStack() {} |     ControlStack() {} | ||||||
|  |  | ||||||
|     ControlStack(const ControlStack& cp) { |     ControlStack(const ControlStack& cp) = default; | ||||||
|         index = cp.index; |  | ||||||
|         std::memcpy(stack.data(), cp.stack.data(), index * sizeof(u32)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool Compare(const ControlStack& cs) const { |     bool Compare(const ControlStack& cs) const { | ||||||
|         if (index != cs.index) { |         if (index != cs.index) { | ||||||
| @@ -35,6 +40,7 @@ struct ControlStack { | |||||||
|         return std::memcmp(stack.data(), cs.stack.data(), index * sizeof(u32)) == 0; |         return std::memcmp(stack.data(), cs.stack.data(), index * sizeof(u32)) == 0; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// This compare just compares the top of the stack against one another | ||||||
|     bool SoftCompare(const ControlStack& cs) const { |     bool SoftCompare(const ControlStack& cs) const { | ||||||
|         if (index == 0 || cs.index == 0) { |         if (index == 0 || cs.index == 0) { | ||||||
|             return index == cs.index; |             return index == cs.index; | ||||||
| @@ -51,7 +57,7 @@ struct ControlStack { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool Push(u32 address) { |     bool Push(u32 address) { | ||||||
|         if (index >= 20) { |         if (index >= stack.size()) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         stack[index] = address; |         stack[index] = address; | ||||||
| @@ -70,21 +76,23 @@ struct ControlStack { | |||||||
|  |  | ||||||
| struct Query { | struct Query { | ||||||
|     Query() {} |     Query() {} | ||||||
|     Query(const Query& q) : address{q.address}, ssy_stack{q.ssy_stack}, pbk_stack{q.pbk_stack} {} |     Query(const Query& q) = default; | ||||||
|     u32 address; |     u32 address{}; | ||||||
|     ControlStack ssy_stack{}; |     ControlStack ssy_stack{}; | ||||||
|     ControlStack pbk_stack{}; |     ControlStack pbk_stack{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct BlockStack { | struct BlockStack { | ||||||
|     BlockStack() = default; |     BlockStack() = default; | ||||||
|     BlockStack(const BlockStack& b) : ssy_stack{b.ssy_stack}, pbk_stack{b.pbk_stack} {} |     BlockStack(const BlockStack& b) = default; | ||||||
|     BlockStack(const Query& q) : ssy_stack{q.ssy_stack}, pbk_stack{q.pbk_stack} {} |     BlockStack(const Query& q) : ssy_stack{q.ssy_stack}, pbk_stack{q.pbk_stack} {} | ||||||
|     ControlStack ssy_stack{}; |     ControlStack ssy_stack{}; | ||||||
|     ControlStack pbk_stack{}; |     ControlStack pbk_stack{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct BlockBranchInfo { | struct BlockBranchInfo { | ||||||
|  |     BlockBranchInfo() = default; | ||||||
|  |     BlockBranchInfo(const BlockBranchInfo& b) = default; | ||||||
|     Condition condition{}; |     Condition condition{}; | ||||||
|     s32 address{exit_branch}; |     s32 address{exit_branch}; | ||||||
|     bool kill{}; |     bool kill{}; | ||||||
| @@ -94,7 +102,7 @@ struct BlockBranchInfo { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| struct BlockInfo { | struct BlockInfo { | ||||||
|     BlockInfo() {} |     BlockInfo() = default; | ||||||
|     u32 start{}; |     u32 start{}; | ||||||
|     u32 end{}; |     u32 end{}; | ||||||
|     bool visited{}; |     bool visited{}; | ||||||
| @@ -107,24 +115,15 @@ struct BlockInfo { | |||||||
|  |  | ||||||
| struct CFGRebuildState { | struct CFGRebuildState { | ||||||
|     explicit CFGRebuildState(const ProgramCode& program_code, const std::size_t program_size) |     explicit CFGRebuildState(const ProgramCode& program_code, const std::size_t program_size) | ||||||
|         : program_code{program_code}, program_size{program_size} { |         : program_code{program_code}, program_size{program_size} {} | ||||||
|         // queries.clear(); |  | ||||||
|         block_info.clear(); |  | ||||||
|         labels.clear(); |  | ||||||
|         registered.clear(); |  | ||||||
|         ssy_labels.clear(); |  | ||||||
|         pbk_labels.clear(); |  | ||||||
|         inspect_queries.clear(); |  | ||||||
|         queries.clear(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::vector<BlockInfo> block_info{}; |     std::vector<BlockInfo> block_info{}; | ||||||
|     std::list<u32> inspect_queries{}; |     std::list<u32> inspect_queries{}; | ||||||
|     std::list<Query> queries{}; |     std::list<Query> queries{}; | ||||||
|     std::unordered_map<u32, u32> registered{}; |     std::unordered_map<u32, u32> registered{}; | ||||||
|     std::unordered_set<u32> labels{}; |     std::unordered_set<u32> labels{}; | ||||||
|     std::map<u32, u32> ssy_labels; |     std::map<u32, u32> ssy_labels{}; | ||||||
|     std::map<u32, u32> pbk_labels; |     std::map<u32, u32> pbk_labels{}; | ||||||
|     std::unordered_map<u32, BlockStack> stacks{}; |     std::unordered_map<u32, BlockStack> stacks{}; | ||||||
|     const ProgramCode& program_code; |     const ProgramCode& program_code; | ||||||
|     const std::size_t program_size; |     const std::size_t program_size; | ||||||
| @@ -156,7 +155,7 @@ BlockInfo* CreateBlockInfo(CFGRebuildState& state, u32 start, u32 end) { | |||||||
|     auto& it = state.block_info.emplace_back(); |     auto& it = state.block_info.emplace_back(); | ||||||
|     it.start = start; |     it.start = start; | ||||||
|     it.end = end; |     it.end = end; | ||||||
|     u32 index = state.block_info.size() - 1; |     const u32 index = static_cast<u32>(state.block_info.size() - 1); | ||||||
|     state.registered.insert({start, index}); |     state.registered.insert({start, index}); | ||||||
|     return ⁢ |     return ⁢ | ||||||
| } | } | ||||||
| @@ -172,11 +171,10 @@ enum class ParseResult : u32 { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| ParseResult ParseCode(CFGRebuildState& state, u32 address, ParseInfo& parse_info) { | ParseResult ParseCode(CFGRebuildState& state, u32 address, ParseInfo& parse_info) { | ||||||
|  |  | ||||||
|     u32 offset = static_cast<u32>(address); |     u32 offset = static_cast<u32>(address); | ||||||
|     u32 end_address = static_cast<u32>(state.program_size - 10U) * 8U; |     const u32 end_address = static_cast<u32>(state.program_size - 10U) * 8U; | ||||||
|  |  | ||||||
|     auto insert_label = ([](CFGRebuildState& state, u32 address) { |     const auto insert_label = ([](CFGRebuildState& state, u32 address) { | ||||||
|         auto pair = state.labels.emplace(address); |         auto pair = state.labels.emplace(address); | ||||||
|         if (pair.second) { |         if (pair.second) { | ||||||
|             state.inspect_queries.push_back(address); |             state.inspect_queries.push_back(address); | ||||||
| @@ -361,20 +359,18 @@ bool TryInspectAddress(CFGRebuildState& state) { | |||||||
|     if (state.inspect_queries.empty()) { |     if (state.inspect_queries.empty()) { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|     u32 address = state.inspect_queries.front(); |     const u32 address = state.inspect_queries.front(); | ||||||
|     state.inspect_queries.pop_front(); |     state.inspect_queries.pop_front(); | ||||||
|     auto search_result = TryGetBlock(state, address); |     const auto search_result = TryGetBlock(state, address); | ||||||
|     BlockInfo* block_info; |  | ||||||
|     switch (search_result.first) { |     switch (search_result.first) { | ||||||
|     case BlockCollision::Found: { |     case BlockCollision::Found: { | ||||||
|         return true; |         return true; | ||||||
|         break; |  | ||||||
|     } |     } | ||||||
|     case BlockCollision::Inside: { |     case BlockCollision::Inside: { | ||||||
|         // This case is the tricky one: |         // This case is the tricky one: | ||||||
|         // We need to Split the block in 2 sepparate blocks |         // We need to Split the block in 2 sepparate blocks | ||||||
|         auto it = search_result.second; |         auto it = search_result.second; | ||||||
|         block_info = CreateBlockInfo(state, address, it->end); |         BlockInfo* block_info = CreateBlockInfo(state, address, it->end); | ||||||
|         it->end = address - 1; |         it->end = address - 1; | ||||||
|         block_info->branch = it->branch; |         block_info->branch = it->branch; | ||||||
|         BlockBranchInfo forward_branch{}; |         BlockBranchInfo forward_branch{}; | ||||||
| @@ -382,34 +378,32 @@ bool TryInspectAddress(CFGRebuildState& state) { | |||||||
|         forward_branch.ignore = true; |         forward_branch.ignore = true; | ||||||
|         it->branch = forward_branch; |         it->branch = forward_branch; | ||||||
|         return true; |         return true; | ||||||
|         break; |  | ||||||
|     } |     } | ||||||
|     default: |     default: | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|     ParseInfo parse_info; |     ParseInfo parse_info; | ||||||
|     ParseResult parse_result = ParseCode(state, address, parse_info); |     const ParseResult parse_result = ParseCode(state, address, parse_info); | ||||||
|     if (parse_result == ParseResult::AbnormalFlow) { |     if (parse_result == ParseResult::AbnormalFlow) { | ||||||
|         // if it's the end of the program, end it safely |  | ||||||
|         // if it's AbnormalFlow, we end it as false, ending the CFG reconstruction |         // if it's AbnormalFlow, we end it as false, ending the CFG reconstruction | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     block_info = CreateBlockInfo(state, address, parse_info.end_address); |     BlockInfo* block_info = CreateBlockInfo(state, address, parse_info.end_address); | ||||||
|     block_info->branch = parse_info.branch_info; |     block_info->branch = parse_info.branch_info; | ||||||
|     if (parse_info.branch_info.condition.IsUnconditional()) { |     if (parse_info.branch_info.condition.IsUnconditional()) { | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     u32 fallthrough_address = parse_info.end_address + 1; |     const u32 fallthrough_address = parse_info.end_address + 1; | ||||||
|     state.inspect_queries.push_front(fallthrough_address); |     state.inspect_queries.push_front(fallthrough_address); | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool TryQuery(CFGRebuildState& state) { | bool TryQuery(CFGRebuildState& state) { | ||||||
|     auto gather_labels = ([](ControlStack& cc, std::map<u32, u32>& labels, BlockInfo& block) { |     const auto gather_labels = ([](ControlStack& cc, std::map<u32, u32>& labels, BlockInfo& block) { | ||||||
|         auto gather_start = labels.lower_bound(block.start); |         auto gather_start = labels.lower_bound(block.start); | ||||||
|         auto gather_end = labels.upper_bound(block.end); |         const auto gather_end = labels.upper_bound(block.end); | ||||||
|         while (gather_start != gather_end) { |         while (gather_start != gather_end) { | ||||||
|             cc.Push(gather_start->second); |             cc.Push(gather_start->second); | ||||||
|             gather_start++; |             gather_start++; | ||||||
| @@ -419,21 +413,21 @@ bool TryQuery(CFGRebuildState& state) { | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|     Query& q = state.queries.front(); |     Query& q = state.queries.front(); | ||||||
|     u32 block_index = state.registered[q.address]; |     const u32 block_index = state.registered[q.address]; | ||||||
|     BlockInfo& block = state.block_info[block_index]; |     BlockInfo& block = state.block_info[block_index]; | ||||||
|     // If the block is visted, check if the stacks match, else gather the ssy/pbk |     // If the block is visted, check if the stacks match, else gather the ssy/pbk | ||||||
|     // labels into the current stack and look if the branch at the end of the block |     // labels into the current stack and look if the branch at the end of the block | ||||||
|     // consumes a label. Schedule new queries accordingly |     // consumes a label. Schedule new queries accordingly | ||||||
|     if (block.visited) { |     if (block.visited) { | ||||||
|         BlockStack& stack = state.stacks[q.address]; |         BlockStack& stack = state.stacks[q.address]; | ||||||
|         bool all_okay = (stack.ssy_stack.Size() == 0 || q.ssy_stack.Compare(stack.ssy_stack)) && |         const bool all_okay = | ||||||
|                         (stack.pbk_stack.Size() == 0 || q.pbk_stack.Compare(stack.pbk_stack)); |             (stack.ssy_stack.Size() == 0 || q.ssy_stack.Compare(stack.ssy_stack)) && | ||||||
|  |             (stack.pbk_stack.Size() == 0 || q.pbk_stack.Compare(stack.pbk_stack)); | ||||||
|         state.queries.pop_front(); |         state.queries.pop_front(); | ||||||
|         return all_okay; |         return all_okay; | ||||||
|     } |     } | ||||||
|     block.visited = true; |     block.visited = true; | ||||||
|     BlockStack bs{q}; |     state.stacks[q.address] = BlockStack{q}; | ||||||
|     state.stacks[q.address] = bs; |  | ||||||
|     Query q2(q); |     Query q2(q); | ||||||
|     state.queries.pop_front(); |     state.queries.pop_front(); | ||||||
|     gather_labels(q2.ssy_stack, state.ssy_labels, block); |     gather_labels(q2.ssy_stack, state.ssy_labels, block); | ||||||
|   | |||||||
| @@ -1,3 +1,7 @@ | |||||||
|  | // Copyright 2019 yuzu Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <cstring> | #include <cstring> | ||||||
| @@ -20,12 +24,15 @@ struct Condition { | |||||||
|     ConditionCode cc{ConditionCode::T}; |     ConditionCode cc{ConditionCode::T}; | ||||||
|  |  | ||||||
|     bool IsUnconditional() const { |     bool IsUnconditional() const { | ||||||
|         return (predicate == Pred::UnusedIndex) && (cc == ConditionCode::T); |         return predicate == Pred::UnusedIndex && cc == ConditionCode::T; | ||||||
|  |     } | ||||||
|  |     bool operator==(const Condition& other) const { | ||||||
|  |         return std::tie(predicate, cc) == std::tie(other.predicate, other.cc); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct ShaderBlock { | struct ShaderBlock { | ||||||
|     ShaderBlock() {} |     ShaderBlock() = default; | ||||||
|     ShaderBlock(const ShaderBlock& sb) = default; |     ShaderBlock(const ShaderBlock& sb) = default; | ||||||
|     u32 start{}; |     u32 start{}; | ||||||
|     u32 end{}; |     u32 end{}; | ||||||
| @@ -35,11 +42,12 @@ struct ShaderBlock { | |||||||
|         bool kills{}; |         bool kills{}; | ||||||
|         s32 address{}; |         s32 address{}; | ||||||
|         bool operator==(const Branch& b) const { |         bool operator==(const Branch& b) const { | ||||||
|             return std::memcmp(this, &b, sizeof(Branch)) == 0; |             return std::tie(cond, kills, address) == std::tie(b.cond, b.kills, b.address); | ||||||
|         } |         } | ||||||
|     } branch; |     } branch; | ||||||
|     bool operator==(const ShaderBlock& sb) const { |     bool operator==(const ShaderBlock& sb) const { | ||||||
|         return std::memcmp(this, &sb, sizeof(ShaderBlock)) == 0; |         return std::tie(start, end, ignore_branch, branch) == | ||||||
|  |                std::tie(sb.start, sb.end, sb.ignore_branch, sb.branch); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ void ShaderIR::Decode() { | |||||||
|         coverage_end = shader_info.end; |         coverage_end = shader_info.end; | ||||||
|         if (shader_info.decompilable) { |         if (shader_info.decompilable) { | ||||||
|             disable_flow_stack = true; |             disable_flow_stack = true; | ||||||
|             auto insert_block = ([this](NodeBlock& nodes, u32 label) { |             const auto insert_block = ([this](NodeBlock& nodes, u32 label) { | ||||||
|                 if (label == exit_branch) { |                 if (label == exit_branch) { | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
| @@ -88,7 +88,6 @@ void ShaderIR::Decode() { | |||||||
|     for (u32 label = main_offset; label < shader_end; label++) { |     for (u32 label = main_offset; label < shader_end; label++) { | ||||||
|         basic_blocks.insert({label, DecodeRange(label, label + 1)}); |         basic_blocks.insert({label, DecodeRange(label, label + 1)}); | ||||||
|     } |     } | ||||||
|     return; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| NodeBlock ShaderIR::DecodeRange(u32 begin, u32 end) { | NodeBlock ShaderIR::DecodeRange(u32 begin, u32 end) { | ||||||
| @@ -104,16 +103,17 @@ void ShaderIR::DecodeRangeInner(NodeBlock& bb, u32 begin, u32 end) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void ShaderIR::InsertControlFlow(NodeBlock& bb, const ShaderBlock& block) { | void ShaderIR::InsertControlFlow(NodeBlock& bb, const ShaderBlock& block) { | ||||||
|     auto apply_conditions = ([&](const Condition& cond, Node n) -> Node { |     const auto apply_conditions = ([&](const Condition& cond, Node n) -> Node { | ||||||
|         Node result = n; |         Node result = n; | ||||||
|         if (cond.cc != ConditionCode::T) { |         if (cond.cc != ConditionCode::T) { | ||||||
|             result = Conditional(GetConditionCode(cond.cc), {result}); |             result = Conditional(GetConditionCode(cond.cc), {result}); | ||||||
|         } |         } | ||||||
|         if (cond.predicate != Pred::UnusedIndex) { |         if (cond.predicate != Pred::UnusedIndex) { | ||||||
|             u32 pred = static_cast<u32>(cond.predicate); |             u32 pred = static_cast<u32>(cond.predicate); | ||||||
|             bool is_neg = pred > 7; |             const bool is_neg = pred > 7; | ||||||
|             if (is_neg) |             if (is_neg) { | ||||||
|                 pred -= 8; |                 pred -= 8; | ||||||
|  |             } | ||||||
|             result = Conditional(GetPredicate(pred, is_neg), {result}); |             result = Conditional(GetPredicate(pred, is_neg), {result}); | ||||||
|         } |         } | ||||||
|         return result; |         return result; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user