Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rpcs3/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ if(BUILD_RPCS3_TESTS)
tests/test_tuple.cpp
tests/test_simple_array.cpp
tests/test_address_range.cpp
tests/test_rsx_cfg.cpp
)

target_link_libraries(rpcs3_test
Expand Down
1 change: 1 addition & 0 deletions rpcs3/Emu/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ target_sources(rpcs3_emu PRIVATE
RSX/Overlays/overlay_video.cpp
RSX/Overlays/Shaders/shader_loading_dialog.cpp
RSX/Overlays/Shaders/shader_loading_dialog_native.cpp
RSX/Program/Assembler/FPToCFG.cpp
RSX/Program/CgBinaryProgram.cpp
RSX/Program/CgBinaryFragmentProgram.cpp
RSX/Program/CgBinaryVertexProgram.cpp
Expand Down
50 changes: 50 additions & 0 deletions rpcs3/Emu/RSX/Common/simple_array.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ namespace rsx
{ c.size() } -> std::integral;
};

template <typename T, typename U>
concept is_trivially_comparable_v =
requires (T t1, U t2) {
{ t1 == t2 } -> std::same_as<bool>;
};

template <typename Ty, size_t Align=alignof(Ty)>
requires std::is_trivially_destructible_v<Ty> && std::is_trivially_copyable_v<Ty>
struct simple_array
Expand Down Expand Up @@ -492,6 +498,50 @@ namespace rsx
return false;
}

/**
* Note that find and find_if return pointers to objects and not iterators for simplified usage.
* It is functionally equivalent to retrieve a nullptr meaning empty object stored and nullptr meaning not found for all practical uses of this container.
*/
template <typename T = Ty>
requires is_trivially_comparable_v<Ty, T>
Ty* find(const T& value)
{
for (auto it = begin(); it != end(); ++it)
{
if (*it == value)
{
return &(*it);
}
}
return nullptr;
}

// Remove when we switch to C++23
template <typename T = Ty>
requires is_trivially_comparable_v<Ty, T>
const Ty* find(const T& value) const
{
return const_cast<simple_array<Ty, Align>*>(this)->find(value);
}

Ty* find_if(std::predicate<const Ty&> auto predicate)
{
for (auto it = begin(); it != end(); ++it)
{
if (std::invoke(predicate, *it))
{
return &(*it);
}
}
return nullptr;
}

// Remove with C++23
const Ty* find_if(std::predicate<const Ty&> auto predicate) const
{
return const_cast<simple_array<Ty, Align>*>(this)->find_if(predicate);
}

bool erase_if(std::predicate<const Ty&> auto predicate)
{
if (!_size)
Expand Down
39 changes: 39 additions & 0 deletions rpcs3/Emu/RSX/Program/Assembler/CFG.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once

#include <util/asm.hpp>
#include "IR.h"

#include <list>

struct RSXFragmentProgram;

namespace rsx::assembler
{
struct FlowGraph
{
std::list<BasicBlock> blocks;

BasicBlock* push(BasicBlock* parent = nullptr, u32 pc = 0, EdgeType edge_type = EdgeType::NONE)
{
if (!parent && !blocks.empty())
{
parent = &blocks.back();
}

blocks.push_back({});
BasicBlock* new_block = &blocks.back();

if (parent)
{
parent->insert_succ(new_block, edge_type);
new_block->insert_pred(parent, edge_type);
}

new_block->id = pc;
return new_block;
}
};

FlowGraph deconstruct_fragment_program(const RSXFragmentProgram& prog);
}

194 changes: 194 additions & 0 deletions rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#include "stdafx.h"

#include "CFG.h"

#include "Emu/RSX/Common/simple_array.hpp"
#include "Emu/RSX/Program/RSXFragmentProgram.h"

#include <util/asm.hpp>
#include <util/v128.hpp>
#include <span>

#if defined(ARCH_ARM64)
#if !defined(_MSC_VER)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
#undef FORCE_INLINE
#include "Emu/CPU/sse2neon.h"
#if !defined(_MSC_VER)
#pragma GCC diagnostic pop
#endif
#endif

namespace rsx::assembler
{
inline v128 decode_instruction(const v128& raw_inst)
{
// Fixup of RSX's weird half-word shuffle for FP instructions
// Convert input stream into LE u16 array
__m128i _mask0 = _mm_set1_epi32(0xff00ff00);
__m128i _mask1 = _mm_set1_epi32(0x00ff00ff);
__m128i a = _mm_slli_epi32(static_cast<__m128i>(raw_inst), 8);
__m128i b = _mm_srli_epi32(static_cast<__m128i>(raw_inst), 8);
__m128i ret = _mm_or_si128(
_mm_and_si128(_mask0, a),
_mm_and_si128(_mask1, b)
);
return v128::loadu(&ret);
}

FlowGraph deconstruct_fragment_program(const RSXFragmentProgram& prog)
{
// For a flowgraph, we don't care at all about the actual contents, just flow control instructions.
OPDEST dst{};
SRC0 src0{};
SRC1 src1{};
SRC2 src2{};

u32 pc = 0; // Program counter
bool end = false;

// Flow control data
rsx::simple_array<BasicBlock*> end_blocks;
rsx::simple_array<BasicBlock*> else_blocks;

// Data block
u32* data = static_cast<u32*>(prog.get_data());

// Output
FlowGraph graph{};
BasicBlock* bb = graph.push();

auto find_block_for_pc = [&](u32 id) -> BasicBlock*
{
auto found = std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == id));
if (found != graph.blocks.end())
{
return &(*found);
}
return nullptr;
};

auto safe_insert_block = [&](BasicBlock* parent, u32 id, EdgeType edge_type) -> BasicBlock*
{
if (auto found = find_block_for_pc(id))
{
parent->insert_succ(found, edge_type);
found->insert_pred(parent, edge_type);
return found;
}

return graph.push(parent, id, edge_type);
};

auto includes_literal_constant = [&]()
{
return src0.reg_type == RSX_FP_REGISTER_TYPE_CONSTANT ||
src1.reg_type == RSX_FP_REGISTER_TYPE_CONSTANT ||
src2.reg_type == RSX_FP_REGISTER_TYPE_CONSTANT;
};

while (!end)
{
BasicBlock** found = end_blocks.find_if(FN(x->id == pc));

if (!found)
{
found = else_blocks.find_if(FN(x->id == pc));
}

if (found)
{
bb = *found;
}

const v128 raw_inst = v128::loadu(data, pc);
v128 decoded = decode_instruction(raw_inst);

dst.HEX = decoded._u32[0];
src0.HEX = decoded._u32[1];
src1.HEX = decoded._u32[2];
src2.HEX = decoded._u32[3];

const u32 opcode = dst.opcode | (src1.opcode_is_branch << 6);

if (opcode == RSX_FP_OPCODE_NOP)
{
pc++;
continue;
}

end = !!dst.end;

bb->instructions.push_back({});
auto& ir_inst = bb->instructions.back();
std::memcpy(ir_inst.bytecode, &decoded._u32[0], 16);
ir_inst.length = 4;
ir_inst.addr = pc * 16;

switch (opcode)
{
case RSX_FP_OPCODE_BRK:
break;
case RSX_FP_OPCODE_CAL:
// Unimplemented. Also unused by the RSX compiler
fmt::throw_exception("Unimplemented FP CAL instruction.");
break;
case RSX_FP_OPCODE_FENCT:
break;
case RSX_FP_OPCODE_FENCB:
break;
case RSX_FP_OPCODE_RET:
// Outside a subroutine, this doesn't mean much. The main block can conditionally return to stop execution early.
// This will not alter flow control.
break;
case RSX_FP_OPCODE_IFE:
{
// Inserts if and else and end blocks
auto parent = bb;
bb = safe_insert_block(parent, pc + 1, EdgeType::IF);
if (src2.end_offset != src1.else_offset)
{
else_blocks.push_back(safe_insert_block(parent, src1.else_offset >> 2, EdgeType::ELSE));
}
end_blocks.push_back(safe_insert_block(parent, src2.end_offset >> 2, EdgeType::ENDIF));
break;
}
case RSX_FP_OPCODE_LOOP:
case RSX_FP_OPCODE_REP:
{
// Inserts for and end blocks
auto parent = bb;
bb = safe_insert_block(parent, pc + 1, EdgeType::LOOP);
end_blocks.push_back(safe_insert_block(parent, src2.end_offset >> 2, EdgeType::ENDLOOP));
break;
}
default:
if (includes_literal_constant())
{
const v128 constant_literal = v128::loadu(data, pc);
v128 decoded_literal = decode_instruction(constant_literal);

std::memcpy(ir_inst.bytecode + 4, &decoded_literal._u32[0], 16);
ir_inst.length += 4;
pc++;
}
}

pc++;
}

// Sort edges for each block by distance
for (auto& block : graph.blocks)
{
std::sort(block.pred.begin(), block.pred.end(), FN(x.from->id > y.from->id));
std::sort(block.succ.begin(), block.succ.end(), FN(x.to->id < y.to->id));
}

// Sort block nodes by distance
graph.blocks.sort(FN(x.id < y.id));
return graph;
}
}
Loading
Loading