shader: Initial recompiler work
This commit is contained in:
parent
75059c46d6
commit
2d48a7b4d0
|
@ -142,6 +142,7 @@ add_subdirectory(core)
|
|||
add_subdirectory(audio_core)
|
||||
add_subdirectory(video_core)
|
||||
add_subdirectory(input_common)
|
||||
add_subdirectory(shader_recompiler)
|
||||
add_subdirectory(tests)
|
||||
|
||||
if (ENABLE_SDL2)
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
add_executable(shader_recompiler
|
||||
environment.h
|
||||
exception.h
|
||||
file_environment.cpp
|
||||
file_environment.h
|
||||
frontend/ir/attribute.cpp
|
||||
frontend/ir/attribute.h
|
||||
frontend/ir/basic_block.cpp
|
||||
frontend/ir/basic_block.h
|
||||
frontend/ir/condition.cpp
|
||||
frontend/ir/condition.h
|
||||
frontend/ir/flow_test.cpp
|
||||
frontend/ir/flow_test.h
|
||||
frontend/ir/ir_emitter.cpp
|
||||
frontend/ir/ir_emitter.h
|
||||
frontend/ir/microinstruction.cpp
|
||||
frontend/ir/microinstruction.h
|
||||
frontend/ir/opcode.cpp
|
||||
frontend/ir/opcode.h
|
||||
frontend/ir/opcode.inc
|
||||
frontend/ir/pred.h
|
||||
frontend/ir/reg.h
|
||||
frontend/ir/type.cpp
|
||||
frontend/ir/type.h
|
||||
frontend/ir/value.cpp
|
||||
frontend/ir/value.h
|
||||
frontend/maxwell/control_flow.cpp
|
||||
frontend/maxwell/control_flow.h
|
||||
frontend/maxwell/decode.cpp
|
||||
frontend/maxwell/decode.h
|
||||
frontend/maxwell/instruction.h
|
||||
frontend/maxwell/location.h
|
||||
frontend/maxwell/maxwell.inc
|
||||
frontend/maxwell/opcode.cpp
|
||||
frontend/maxwell/opcode.h
|
||||
frontend/maxwell/program.cpp
|
||||
frontend/maxwell/program.h
|
||||
frontend/maxwell/termination_code.cpp
|
||||
frontend/maxwell/termination_code.h
|
||||
frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp
|
||||
frontend/maxwell/translate/impl/floating_point_multi_function.cpp
|
||||
frontend/maxwell/translate/impl/impl.cpp
|
||||
frontend/maxwell/translate/impl/impl.h
|
||||
frontend/maxwell/translate/impl/load_store_attribute.cpp
|
||||
frontend/maxwell/translate/impl/load_store_memory.cpp
|
||||
frontend/maxwell/translate/impl/not_implemented.cpp
|
||||
frontend/maxwell/translate/impl/register_move.cpp
|
||||
frontend/maxwell/translate/translate.cpp
|
||||
frontend/maxwell/translate/translate.h
|
||||
ir_opt/dead_code_elimination_pass.cpp
|
||||
ir_opt/get_set_elimination_pass.cpp
|
||||
ir_opt/identity_removal_pass.cpp
|
||||
ir_opt/passes.h
|
||||
ir_opt/verification_pass.cpp
|
||||
main.cpp
|
||||
)
|
||||
target_link_libraries(shader_recompiler PRIVATE fmt::fmt)
|
||||
|
||||
if (MSVC)
|
||||
target_compile_options(shader_recompiler PRIVATE
|
||||
/W4
|
||||
/WX
|
||||
/we4018 # 'expression' : signed/unsigned mismatch
|
||||
/we4244 # 'argument' : conversion from 'type1' to 'type2', possible loss of data (floating-point)
|
||||
/we4245 # 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch
|
||||
/we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
|
||||
/we4267 # 'var' : conversion from 'size_t' to 'type', possible loss of data
|
||||
/we4305 # 'context' : truncation from 'type1' to 'type2'
|
||||
/we4800 # Implicit conversion from 'type' to bool. Possible information loss
|
||||
/we4826 # Conversion from 'type1' to 'type2' is sign-extended. This may cause unexpected runtime behavior.
|
||||
)
|
||||
else()
|
||||
target_compile_options(shader_recompiler PRIVATE
|
||||
-Werror
|
||||
-Werror=conversion
|
||||
-Werror=ignored-qualifiers
|
||||
-Werror=implicit-fallthrough
|
||||
-Werror=shadow
|
||||
-Werror=sign-compare
|
||||
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
|
||||
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
|
||||
-Werror=unused-variable
|
||||
)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(shader_recompiler)
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Shader {
|
||||
|
||||
class Environment {
|
||||
public:
|
||||
virtual ~Environment() = default;
|
||||
|
||||
[[nodiscard]] virtual u64 ReadInstruction(u32 address) const = 0;
|
||||
};
|
||||
|
||||
} // namespace Shader
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Shader {
|
||||
|
||||
class LogicError : public std::logic_error {
|
||||
public:
|
||||
template <typename... Args>
|
||||
LogicError(const char* message, Args&&... args)
|
||||
: std::logic_error{fmt::format(message, std::forward<Args>(args)...)} {}
|
||||
};
|
||||
|
||||
class RuntimeError : public std::runtime_error {
|
||||
public:
|
||||
template <typename... Args>
|
||||
RuntimeError(const char* message, Args&&... args)
|
||||
: std::runtime_error{fmt::format(message, std::forward<Args>(args)...)} {}
|
||||
};
|
||||
|
||||
class NotImplementedException : public std::logic_error {
|
||||
public:
|
||||
template <typename... Args>
|
||||
NotImplementedException(const char* message, Args&&... args)
|
||||
: std::logic_error{fmt::format(message, std::forward<Args>(args)...)} {}
|
||||
};
|
||||
|
||||
class InvalidArgument : public std::invalid_argument {
|
||||
public:
|
||||
template <typename... Args>
|
||||
InvalidArgument(const char* message, Args&&... args)
|
||||
: std::invalid_argument{fmt::format(message, std::forward<Args>(args)...)} {}
|
||||
};
|
||||
|
||||
} // namespace Shader
|
|
@ -0,0 +1,42 @@
|
|||
#include <cstdio>
|
||||
|
||||
#include "exception.h"
|
||||
#include "file_environment.h"
|
||||
|
||||
namespace Shader {
|
||||
|
||||
FileEnvironment::FileEnvironment(const char* path) {
|
||||
std::FILE* const file{std::fopen(path, "rb")};
|
||||
if (!file) {
|
||||
throw RuntimeError("Failed to open file='{}'", path);
|
||||
}
|
||||
std::fseek(file, 0, SEEK_END);
|
||||
const long size{std::ftell(file)};
|
||||
std::rewind(file);
|
||||
if (size % 8 != 0) {
|
||||
std::fclose(file);
|
||||
throw RuntimeError("File size={} is not aligned to 8", size);
|
||||
}
|
||||
// TODO: Use a unique_ptr to avoid zero-initializing this
|
||||
const size_t num_inst{static_cast<size_t>(size) / 8};
|
||||
data.resize(num_inst);
|
||||
if (std::fread(data.data(), 8, num_inst, file) != num_inst) {
|
||||
std::fclose(file);
|
||||
throw RuntimeError("Failed to read instructions={} from file='{}'", num_inst, path);
|
||||
}
|
||||
std::fclose(file);
|
||||
}
|
||||
|
||||
FileEnvironment::~FileEnvironment() = default;
|
||||
|
||||
u64 FileEnvironment::ReadInstruction(u32 offset) const {
|
||||
if (offset % 8 != 0) {
|
||||
throw InvalidArgument("offset={} is not aligned to 8", offset);
|
||||
}
|
||||
if (offset / 8 >= static_cast<u32>(data.size())) {
|
||||
throw InvalidArgument("offset={} is out of bounds", offset);
|
||||
}
|
||||
return data[offset / 8];
|
||||
}
|
||||
|
||||
} // namespace Shader
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "environment.h"
|
||||
|
||||
namespace Shader {
|
||||
|
||||
class FileEnvironment final : public Environment {
|
||||
public:
|
||||
explicit FileEnvironment(const char* path);
|
||||
~FileEnvironment() override;
|
||||
|
||||
u64 ReadInstruction(u32 offset) const override;
|
||||
|
||||
private:
|
||||
std::vector<u64> data;
|
||||
};
|
||||
|
||||
} // namespace Shader
|
|
@ -0,0 +1,447 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/ir/attribute.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
bool IsGeneric(Attribute attribute) noexcept {
|
||||
return attribute >= Attribute::Generic0X && attribute <= Attribute::Generic31X;
|
||||
}
|
||||
|
||||
int GenericAttributeIndex(Attribute attribute) {
|
||||
if (!IsGeneric(attribute)) {
|
||||
throw InvalidArgument("Attribute is not generic {}", attribute);
|
||||
}
|
||||
return (static_cast<int>(attribute) - static_cast<int>(Attribute::Generic0X)) / 4;
|
||||
}
|
||||
|
||||
std::string NameOf(Attribute attribute) {
|
||||
switch (attribute) {
|
||||
case Attribute::PrimitiveId:
|
||||
return "PrimitiveId";
|
||||
case Attribute::Layer:
|
||||
return "Layer";
|
||||
case Attribute::ViewportIndex:
|
||||
return "ViewportIndex";
|
||||
case Attribute::PointSize:
|
||||
return "PointSize";
|
||||
case Attribute::PositionX:
|
||||
return "Position.X";
|
||||
case Attribute::PositionY:
|
||||
return "Position.Y";
|
||||
case Attribute::PositionZ:
|
||||
return "Position.Z";
|
||||
case Attribute::PositionW:
|
||||
return "Position.W";
|
||||
case Attribute::Generic0X:
|
||||
return "Generic[0].X";
|
||||
case Attribute::Generic0Y:
|
||||
return "Generic[0].Y";
|
||||
case Attribute::Generic0Z:
|
||||
return "Generic[0].Z";
|
||||
case Attribute::Generic0W:
|
||||
return "Generic[0].W";
|
||||
case Attribute::Generic1X:
|
||||
return "Generic[1].X";
|
||||
case Attribute::Generic1Y:
|
||||
return "Generic[1].Y";
|
||||
case Attribute::Generic1Z:
|
||||
return "Generic[1].Z";
|
||||
case Attribute::Generic1W:
|
||||
return "Generic[1].W";
|
||||
case Attribute::Generic2X:
|
||||
return "Generic[2].X";
|
||||
case Attribute::Generic2Y:
|
||||
return "Generic[2].Y";
|
||||
case Attribute::Generic2Z:
|
||||
return "Generic[2].Z";
|
||||
case Attribute::Generic2W:
|
||||
return "Generic[2].W";
|
||||
case Attribute::Generic3X:
|
||||
return "Generic[3].X";
|
||||
case Attribute::Generic3Y:
|
||||
return "Generic[3].Y";
|
||||
case Attribute::Generic3Z:
|
||||
return "Generic[3].Z";
|
||||
case Attribute::Generic3W:
|
||||
return "Generic[3].W";
|
||||
case Attribute::Generic4X:
|
||||
return "Generic[4].X";
|
||||
case Attribute::Generic4Y:
|
||||
return "Generic[4].Y";
|
||||
case Attribute::Generic4Z:
|
||||
return "Generic[4].Z";
|
||||
case Attribute::Generic4W:
|
||||
return "Generic[4].W";
|
||||
case Attribute::Generic5X:
|
||||
return "Generic[5].X";
|
||||
case Attribute::Generic5Y:
|
||||
return "Generic[5].Y";
|
||||
case Attribute::Generic5Z:
|
||||
return "Generic[5].Z";
|
||||
case Attribute::Generic5W:
|
||||
return "Generic[5].W";
|
||||
case Attribute::Generic6X:
|
||||
return "Generic[6].X";
|
||||
case Attribute::Generic6Y:
|
||||
return "Generic[6].Y";
|
||||
case Attribute::Generic6Z:
|
||||
return "Generic[6].Z";
|
||||
case Attribute::Generic6W:
|
||||
return "Generic[6].W";
|
||||
case Attribute::Generic7X:
|
||||
return "Generic[7].X";
|
||||
case Attribute::Generic7Y:
|
||||
return "Generic[7].Y";
|
||||
case Attribute::Generic7Z:
|
||||
return "Generic[7].Z";
|
||||
case Attribute::Generic7W:
|
||||
return "Generic[7].W";
|
||||
case Attribute::Generic8X:
|
||||
return "Generic[8].X";
|
||||
case Attribute::Generic8Y:
|
||||
return "Generic[8].Y";
|
||||
case Attribute::Generic8Z:
|
||||
return "Generic[8].Z";
|
||||
case Attribute::Generic8W:
|
||||
return "Generic[8].W";
|
||||
case Attribute::Generic9X:
|
||||
return "Generic[9].X";
|
||||
case Attribute::Generic9Y:
|
||||
return "Generic[9].Y";
|
||||
case Attribute::Generic9Z:
|
||||
return "Generic[9].Z";
|
||||
case Attribute::Generic9W:
|
||||
return "Generic[9].W";
|
||||
case Attribute::Generic10X:
|
||||
return "Generic[10].X";
|
||||
case Attribute::Generic10Y:
|
||||
return "Generic[10].Y";
|
||||
case Attribute::Generic10Z:
|
||||
return "Generic[10].Z";
|
||||
case Attribute::Generic10W:
|
||||
return "Generic[10].W";
|
||||
case Attribute::Generic11X:
|
||||
return "Generic[11].X";
|
||||
case Attribute::Generic11Y:
|
||||
return "Generic[11].Y";
|
||||
case Attribute::Generic11Z:
|
||||
return "Generic[11].Z";
|
||||
case Attribute::Generic11W:
|
||||
return "Generic[11].W";
|
||||
case Attribute::Generic12X:
|
||||
return "Generic[12].X";
|
||||
case Attribute::Generic12Y:
|
||||
return "Generic[12].Y";
|
||||
case Attribute::Generic12Z:
|
||||
return "Generic[12].Z";
|
||||
case Attribute::Generic12W:
|
||||
return "Generic[12].W";
|
||||
case Attribute::Generic13X:
|
||||
return "Generic[13].X";
|
||||
case Attribute::Generic13Y:
|
||||
return "Generic[13].Y";
|
||||
case Attribute::Generic13Z:
|
||||
return "Generic[13].Z";
|
||||
case Attribute::Generic13W:
|
||||
return "Generic[13].W";
|
||||
case Attribute::Generic14X:
|
||||
return "Generic[14].X";
|
||||
case Attribute::Generic14Y:
|
||||
return "Generic[14].Y";
|
||||
case Attribute::Generic14Z:
|
||||
return "Generic[14].Z";
|
||||
case Attribute::Generic14W:
|
||||
return "Generic[14].W";
|
||||
case Attribute::Generic15X:
|
||||
return "Generic[15].X";
|
||||
case Attribute::Generic15Y:
|
||||
return "Generic[15].Y";
|
||||
case Attribute::Generic15Z:
|
||||
return "Generic[15].Z";
|
||||
case Attribute::Generic15W:
|
||||
return "Generic[15].W";
|
||||
case Attribute::Generic16X:
|
||||
return "Generic[16].X";
|
||||
case Attribute::Generic16Y:
|
||||
return "Generic[16].Y";
|
||||
case Attribute::Generic16Z:
|
||||
return "Generic[16].Z";
|
||||
case Attribute::Generic16W:
|
||||
return "Generic[16].W";
|
||||
case Attribute::Generic17X:
|
||||
return "Generic[17].X";
|
||||
case Attribute::Generic17Y:
|
||||
return "Generic[17].Y";
|
||||
case Attribute::Generic17Z:
|
||||
return "Generic[17].Z";
|
||||
case Attribute::Generic17W:
|
||||
return "Generic[17].W";
|
||||
case Attribute::Generic18X:
|
||||
return "Generic[18].X";
|
||||
case Attribute::Generic18Y:
|
||||
return "Generic[18].Y";
|
||||
case Attribute::Generic18Z:
|
||||
return "Generic[18].Z";
|
||||
case Attribute::Generic18W:
|
||||
return "Generic[18].W";
|
||||
case Attribute::Generic19X:
|
||||
return "Generic[19].X";
|
||||
case Attribute::Generic19Y:
|
||||
return "Generic[19].Y";
|
||||
case Attribute::Generic19Z:
|
||||
return "Generic[19].Z";
|
||||
case Attribute::Generic19W:
|
||||
return "Generic[19].W";
|
||||
case Attribute::Generic20X:
|
||||
return "Generic[20].X";
|
||||
case Attribute::Generic20Y:
|
||||
return "Generic[20].Y";
|
||||
case Attribute::Generic20Z:
|
||||
return "Generic[20].Z";
|
||||
case Attribute::Generic20W:
|
||||
return "Generic[20].W";
|
||||
case Attribute::Generic21X:
|
||||
return "Generic[21].X";
|
||||
case Attribute::Generic21Y:
|
||||
return "Generic[21].Y";
|
||||
case Attribute::Generic21Z:
|
||||
return "Generic[21].Z";
|
||||
case Attribute::Generic21W:
|
||||
return "Generic[21].W";
|
||||
case Attribute::Generic22X:
|
||||
return "Generic[22].X";
|
||||
case Attribute::Generic22Y:
|
||||
return "Generic[22].Y";
|
||||
case Attribute::Generic22Z:
|
||||
return "Generic[22].Z";
|
||||
case Attribute::Generic22W:
|
||||
return "Generic[22].W";
|
||||
case Attribute::Generic23X:
|
||||
return "Generic[23].X";
|
||||
case Attribute::Generic23Y:
|
||||
return "Generic[23].Y";
|
||||
case Attribute::Generic23Z:
|
||||
return "Generic[23].Z";
|
||||
case Attribute::Generic23W:
|
||||
return "Generic[23].W";
|
||||
case Attribute::Generic24X:
|
||||
return "Generic[24].X";
|
||||
case Attribute::Generic24Y:
|
||||
return "Generic[24].Y";
|
||||
case Attribute::Generic24Z:
|
||||
return "Generic[24].Z";
|
||||
case Attribute::Generic24W:
|
||||
return "Generic[24].W";
|
||||
case Attribute::Generic25X:
|
||||
return "Generic[25].X";
|
||||
case Attribute::Generic25Y:
|
||||
return "Generic[25].Y";
|
||||
case Attribute::Generic25Z:
|
||||
return "Generic[25].Z";
|
||||
case Attribute::Generic25W:
|
||||
return "Generic[25].W";
|
||||
case Attribute::Generic26X:
|
||||
return "Generic[26].X";
|
||||
case Attribute::Generic26Y:
|
||||
return "Generic[26].Y";
|
||||
case Attribute::Generic26Z:
|
||||
return "Generic[26].Z";
|
||||
case Attribute::Generic26W:
|
||||
return "Generic[26].W";
|
||||
case Attribute::Generic27X:
|
||||
return "Generic[27].X";
|
||||
case Attribute::Generic27Y:
|
||||
return "Generic[27].Y";
|
||||
case Attribute::Generic27Z:
|
||||
return "Generic[27].Z";
|
||||
case Attribute::Generic27W:
|
||||
return "Generic[27].W";
|
||||
case Attribute::Generic28X:
|
||||
return "Generic[28].X";
|
||||
case Attribute::Generic28Y:
|
||||
return "Generic[28].Y";
|
||||
case Attribute::Generic28Z:
|
||||
return "Generic[28].Z";
|
||||
case Attribute::Generic28W:
|
||||
return "Generic[28].W";
|
||||
case Attribute::Generic29X:
|
||||
return "Generic[29].X";
|
||||
case Attribute::Generic29Y:
|
||||
return "Generic[29].Y";
|
||||
case Attribute::Generic29Z:
|
||||
return "Generic[29].Z";
|
||||
case Attribute::Generic29W:
|
||||
return "Generic[29].W";
|
||||
case Attribute::Generic30X:
|
||||
return "Generic[30].X";
|
||||
case Attribute::Generic30Y:
|
||||
return "Generic[30].Y";
|
||||
case Attribute::Generic30Z:
|
||||
return "Generic[30].Z";
|
||||
case Attribute::Generic30W:
|
||||
return "Generic[30].W";
|
||||
case Attribute::Generic31X:
|
||||
return "Generic[31].X";
|
||||
case Attribute::Generic31Y:
|
||||
return "Generic[31].Y";
|
||||
case Attribute::Generic31Z:
|
||||
return "Generic[31].Z";
|
||||
case Attribute::Generic31W:
|
||||
return "Generic[31].W";
|
||||
case Attribute::ColorFrontDiffuseR:
|
||||
return "ColorFrontDiffuse.R";
|
||||
case Attribute::ColorFrontDiffuseG:
|
||||
return "ColorFrontDiffuse.G";
|
||||
case Attribute::ColorFrontDiffuseB:
|
||||
return "ColorFrontDiffuse.B";
|
||||
case Attribute::ColorFrontDiffuseA:
|
||||
return "ColorFrontDiffuse.A";
|
||||
case Attribute::ColorFrontSpecularR:
|
||||
return "ColorFrontSpecular.R";
|
||||
case Attribute::ColorFrontSpecularG:
|
||||
return "ColorFrontSpecular.G";
|
||||
case Attribute::ColorFrontSpecularB:
|
||||
return "ColorFrontSpecular.B";
|
||||
case Attribute::ColorFrontSpecularA:
|
||||
return "ColorFrontSpecular.A";
|
||||
case Attribute::ColorBackDiffuseR:
|
||||
return "ColorBackDiffuse.R";
|
||||
case Attribute::ColorBackDiffuseG:
|
||||
return "ColorBackDiffuse.G";
|
||||
case Attribute::ColorBackDiffuseB:
|
||||
return "ColorBackDiffuse.B";
|
||||
case Attribute::ColorBackDiffuseA:
|
||||
return "ColorBackDiffuse.A";
|
||||
case Attribute::ColorBackSpecularR:
|
||||
return "ColorBackSpecular.R";
|
||||
case Attribute::ColorBackSpecularG:
|
||||
return "ColorBackSpecular.G";
|
||||
case Attribute::ColorBackSpecularB:
|
||||
return "ColorBackSpecular.B";
|
||||
case Attribute::ColorBackSpecularA:
|
||||
return "ColorBackSpecular.A";
|
||||
case Attribute::ClipDistance0:
|
||||
return "ClipDistance[0]";
|
||||
case Attribute::ClipDistance1:
|
||||
return "ClipDistance[1]";
|
||||
case Attribute::ClipDistance2:
|
||||
return "ClipDistance[2]";
|
||||
case Attribute::ClipDistance3:
|
||||
return "ClipDistance[3]";
|
||||
case Attribute::ClipDistance4:
|
||||
return "ClipDistance[4]";
|
||||
case Attribute::ClipDistance5:
|
||||
return "ClipDistance[5]";
|
||||
case Attribute::ClipDistance6:
|
||||
return "ClipDistance[6]";
|
||||
case Attribute::ClipDistance7:
|
||||
return "ClipDistance[7]";
|
||||
case Attribute::PointSpriteS:
|
||||
return "PointSprite.S";
|
||||
case Attribute::PointSpriteT:
|
||||
return "PointSprite.T";
|
||||
case Attribute::FogCoordinate:
|
||||
return "FogCoordinate";
|
||||
case Attribute::TessellationEvaluationPointU:
|
||||
return "TessellationEvaluationPoint.U";
|
||||
case Attribute::TessellationEvaluationPointV:
|
||||
return "TessellationEvaluationPoint.V";
|
||||
case Attribute::InstanceId:
|
||||
return "InstanceId";
|
||||
case Attribute::VertexId:
|
||||
return "VertexId";
|
||||
case Attribute::FixedFncTexture0S:
|
||||
return "FixedFncTexture[0].S";
|
||||
case Attribute::FixedFncTexture0T:
|
||||
return "FixedFncTexture[0].T";
|
||||
case Attribute::FixedFncTexture0R:
|
||||
return "FixedFncTexture[0].R";
|
||||
case Attribute::FixedFncTexture0Q:
|
||||
return "FixedFncTexture[0].Q";
|
||||
case Attribute::FixedFncTexture1S:
|
||||
return "FixedFncTexture[1].S";
|
||||
case Attribute::FixedFncTexture1T:
|
||||
return "FixedFncTexture[1].T";
|
||||
case Attribute::FixedFncTexture1R:
|
||||
return "FixedFncTexture[1].R";
|
||||
case Attribute::FixedFncTexture1Q:
|
||||
return "FixedFncTexture[1].Q";
|
||||
case Attribute::FixedFncTexture2S:
|
||||
return "FixedFncTexture[2].S";
|
||||
case Attribute::FixedFncTexture2T:
|
||||
return "FixedFncTexture[2].T";
|
||||
case Attribute::FixedFncTexture2R:
|
||||
return "FixedFncTexture[2].R";
|
||||
case Attribute::FixedFncTexture2Q:
|
||||
return "FixedFncTexture[2].Q";
|
||||
case Attribute::FixedFncTexture3S:
|
||||
return "FixedFncTexture[3].S";
|
||||
case Attribute::FixedFncTexture3T:
|
||||
return "FixedFncTexture[3].T";
|
||||
case Attribute::FixedFncTexture3R:
|
||||
return "FixedFncTexture[3].R";
|
||||
case Attribute::FixedFncTexture3Q:
|
||||
return "FixedFncTexture[3].Q";
|
||||
case Attribute::FixedFncTexture4S:
|
||||
return "FixedFncTexture[4].S";
|
||||
case Attribute::FixedFncTexture4T:
|
||||
return "FixedFncTexture[4].T";
|
||||
case Attribute::FixedFncTexture4R:
|
||||
return "FixedFncTexture[4].R";
|
||||
case Attribute::FixedFncTexture4Q:
|
||||
return "FixedFncTexture[4].Q";
|
||||
case Attribute::FixedFncTexture5S:
|
||||
return "FixedFncTexture[5].S";
|
||||
case Attribute::FixedFncTexture5T:
|
||||
return "FixedFncTexture[5].T";
|
||||
case Attribute::FixedFncTexture5R:
|
||||
return "FixedFncTexture[5].R";
|
||||
case Attribute::FixedFncTexture5Q:
|
||||
return "FixedFncTexture[5].Q";
|
||||
case Attribute::FixedFncTexture6S:
|
||||
return "FixedFncTexture[6].S";
|
||||
case Attribute::FixedFncTexture6T:
|
||||
return "FixedFncTexture[6].T";
|
||||
case Attribute::FixedFncTexture6R:
|
||||
return "FixedFncTexture[6].R";
|
||||
case Attribute::FixedFncTexture6Q:
|
||||
return "FixedFncTexture[6].Q";
|
||||
case Attribute::FixedFncTexture7S:
|
||||
return "FixedFncTexture[7].S";
|
||||
case Attribute::FixedFncTexture7T:
|
||||
return "FixedFncTexture[7].T";
|
||||
case Attribute::FixedFncTexture7R:
|
||||
return "FixedFncTexture[7].R";
|
||||
case Attribute::FixedFncTexture7Q:
|
||||
return "FixedFncTexture[7].Q";
|
||||
case Attribute::FixedFncTexture8S:
|
||||
return "FixedFncTexture[8].S";
|
||||
case Attribute::FixedFncTexture8T:
|
||||
return "FixedFncTexture[8].T";
|
||||
case Attribute::FixedFncTexture8R:
|
||||
return "FixedFncTexture[8].R";
|
||||
case Attribute::FixedFncTexture8Q:
|
||||
return "FixedFncTexture[8].Q";
|
||||
case Attribute::FixedFncTexture9S:
|
||||
return "FixedFncTexture[9].S";
|
||||
case Attribute::FixedFncTexture9T:
|
||||
return "FixedFncTexture[9].T";
|
||||
case Attribute::FixedFncTexture9R:
|
||||
return "FixedFncTexture[9].R";
|
||||
case Attribute::FixedFncTexture9Q:
|
||||
return "FixedFncTexture[9].Q";
|
||||
case Attribute::ViewportMask:
|
||||
return "ViewportMask";
|
||||
case Attribute::FrontFace:
|
||||
return "FrontFace";
|
||||
}
|
||||
return fmt::format("<reserved attribute {}>", static_cast<int>(attribute));
|
||||
}
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,242 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
enum class Attribute : u64 {
|
||||
PrimitiveId = 24,
|
||||
Layer = 25,
|
||||
ViewportIndex = 26,
|
||||
PointSize = 27,
|
||||
PositionX = 28,
|
||||
PositionY = 29,
|
||||
PositionZ = 30,
|
||||
PositionW = 31,
|
||||
Generic0X = 32,
|
||||
Generic0Y = 33,
|
||||
Generic0Z = 34,
|
||||
Generic0W = 35,
|
||||
Generic1X = 36,
|
||||
Generic1Y = 37,
|
||||
Generic1Z = 38,
|
||||
Generic1W = 39,
|
||||
Generic2X = 40,
|
||||
Generic2Y = 41,
|
||||
Generic2Z = 42,
|
||||
Generic2W = 43,
|
||||
Generic3X = 44,
|
||||
Generic3Y = 45,
|
||||
Generic3Z = 46,
|
||||
Generic3W = 47,
|
||||
Generic4X = 48,
|
||||
Generic4Y = 49,
|
||||
Generic4Z = 50,
|
||||
Generic4W = 51,
|
||||
Generic5X = 52,
|
||||
Generic5Y = 53,
|
||||
Generic5Z = 54,
|
||||
Generic5W = 55,
|
||||
Generic6X = 56,
|
||||
Generic6Y = 57,
|
||||
Generic6Z = 58,
|
||||
Generic6W = 59,
|
||||
Generic7X = 60,
|
||||
Generic7Y = 61,
|
||||
Generic7Z = 62,
|
||||
Generic7W = 63,
|
||||
Generic8X = 64,
|
||||
Generic8Y = 65,
|
||||
Generic8Z = 66,
|
||||
Generic8W = 67,
|
||||
Generic9X = 68,
|
||||
Generic9Y = 69,
|
||||
Generic9Z = 70,
|
||||
Generic9W = 71,
|
||||
Generic10X = 72,
|
||||
Generic10Y = 73,
|
||||
Generic10Z = 74,
|
||||
Generic10W = 75,
|
||||
Generic11X = 76,
|
||||
Generic11Y = 77,
|
||||
Generic11Z = 78,
|
||||
Generic11W = 79,
|
||||
Generic12X = 80,
|
||||
Generic12Y = 81,
|
||||
Generic12Z = 82,
|
||||
Generic12W = 83,
|
||||
Generic13X = 84,
|
||||
Generic13Y = 85,
|
||||
Generic13Z = 86,
|
||||
Generic13W = 87,
|
||||
Generic14X = 88,
|
||||
Generic14Y = 89,
|
||||
Generic14Z = 90,
|
||||
Generic14W = 91,
|
||||
Generic15X = 92,
|
||||
Generic15Y = 93,
|
||||
Generic15Z = 94,
|
||||
Generic15W = 95,
|
||||
Generic16X = 96,
|
||||
Generic16Y = 97,
|
||||
Generic16Z = 98,
|
||||
Generic16W = 99,
|
||||
Generic17X = 100,
|
||||
Generic17Y = 101,
|
||||
Generic17Z = 102,
|
||||
Generic17W = 103,
|
||||
Generic18X = 104,
|
||||
Generic18Y = 105,
|
||||
Generic18Z = 106,
|
||||
Generic18W = 107,
|
||||
Generic19X = 108,
|
||||
Generic19Y = 109,
|
||||
Generic19Z = 110,
|
||||
Generic19W = 111,
|
||||
Generic20X = 112,
|
||||
Generic20Y = 113,
|
||||
Generic20Z = 114,
|
||||
Generic20W = 115,
|
||||
Generic21X = 116,
|
||||
Generic21Y = 117,
|
||||
Generic21Z = 118,
|
||||
Generic21W = 119,
|
||||
Generic22X = 120,
|
||||
Generic22Y = 121,
|
||||
Generic22Z = 122,
|
||||
Generic22W = 123,
|
||||
Generic23X = 124,
|
||||
Generic23Y = 125,
|
||||
Generic23Z = 126,
|
||||
Generic23W = 127,
|
||||
Generic24X = 128,
|
||||
Generic24Y = 129,
|
||||
Generic24Z = 130,
|
||||
Generic24W = 131,
|
||||
Generic25X = 132,
|
||||
Generic25Y = 133,
|
||||
Generic25Z = 134,
|
||||
Generic25W = 135,
|
||||
Generic26X = 136,
|
||||
Generic26Y = 137,
|
||||
Generic26Z = 138,
|
||||
Generic26W = 139,
|
||||
Generic27X = 140,
|
||||
Generic27Y = 141,
|
||||
Generic27Z = 142,
|
||||
Generic27W = 143,
|
||||
Generic28X = 144,
|
||||
Generic28Y = 145,
|
||||
Generic28Z = 146,
|
||||
Generic28W = 147,
|
||||
Generic29X = 148,
|
||||
Generic29Y = 149,
|
||||
Generic29Z = 150,
|
||||
Generic29W = 151,
|
||||
Generic30X = 152,
|
||||
Generic30Y = 153,
|
||||
Generic30Z = 154,
|
||||
Generic30W = 155,
|
||||
Generic31X = 156,
|
||||
Generic31Y = 157,
|
||||
Generic31Z = 158,
|
||||
Generic31W = 159,
|
||||
ColorFrontDiffuseR = 160,
|
||||
ColorFrontDiffuseG = 161,
|
||||
ColorFrontDiffuseB = 162,
|
||||
ColorFrontDiffuseA = 163,
|
||||
ColorFrontSpecularR = 164,
|
||||
ColorFrontSpecularG = 165,
|
||||
ColorFrontSpecularB = 166,
|
||||
ColorFrontSpecularA = 167,
|
||||
ColorBackDiffuseR = 168,
|
||||
ColorBackDiffuseG = 169,
|
||||
ColorBackDiffuseB = 170,
|
||||
ColorBackDiffuseA = 171,
|
||||
ColorBackSpecularR = 172,
|
||||
ColorBackSpecularG = 173,
|
||||
ColorBackSpecularB = 174,
|
||||
ColorBackSpecularA = 175,
|
||||
ClipDistance0 = 176,
|
||||
ClipDistance1 = 177,
|
||||
ClipDistance2 = 178,
|
||||
ClipDistance3 = 179,
|
||||
ClipDistance4 = 180,
|
||||
ClipDistance5 = 181,
|
||||
ClipDistance6 = 182,
|
||||
ClipDistance7 = 183,
|
||||
PointSpriteS = 184,
|
||||
PointSpriteT = 185,
|
||||
FogCoordinate = 186,
|
||||
TessellationEvaluationPointU = 188,
|
||||
TessellationEvaluationPointV = 189,
|
||||
InstanceId = 190,
|
||||
VertexId = 191,
|
||||
FixedFncTexture0S = 192,
|
||||
FixedFncTexture0T = 193,
|
||||
FixedFncTexture0R = 194,
|
||||
FixedFncTexture0Q = 195,
|
||||
FixedFncTexture1S = 196,
|
||||
FixedFncTexture1T = 197,
|
||||
FixedFncTexture1R = 198,
|
||||
FixedFncTexture1Q = 199,
|
||||
FixedFncTexture2S = 200,
|
||||
FixedFncTexture2T = 201,
|
||||
FixedFncTexture2R = 202,
|
||||
FixedFncTexture2Q = 203,
|
||||
FixedFncTexture3S = 204,
|
||||
FixedFncTexture3T = 205,
|
||||
FixedFncTexture3R = 206,
|
||||
FixedFncTexture3Q = 207,
|
||||
FixedFncTexture4S = 208,
|
||||
FixedFncTexture4T = 209,
|
||||
FixedFncTexture4R = 210,
|
||||
FixedFncTexture4Q = 211,
|
||||
FixedFncTexture5S = 212,
|
||||
FixedFncTexture5T = 213,
|
||||
FixedFncTexture5R = 214,
|
||||
FixedFncTexture5Q = 215,
|
||||
FixedFncTexture6S = 216,
|
||||
FixedFncTexture6T = 217,
|
||||
FixedFncTexture6R = 218,
|
||||
FixedFncTexture6Q = 219,
|
||||
FixedFncTexture7S = 220,
|
||||
FixedFncTexture7T = 221,
|
||||
FixedFncTexture7R = 222,
|
||||
FixedFncTexture7Q = 223,
|
||||
FixedFncTexture8S = 224,
|
||||
FixedFncTexture8T = 225,
|
||||
FixedFncTexture8R = 226,
|
||||
FixedFncTexture8Q = 227,
|
||||
FixedFncTexture9S = 228,
|
||||
FixedFncTexture9T = 229,
|
||||
FixedFncTexture9R = 230,
|
||||
FixedFncTexture9Q = 231,
|
||||
ViewportMask = 232,
|
||||
FrontFace = 255,
|
||||
};
|
||||
|
||||
[[nodiscard]] bool IsGeneric(Attribute attribute) noexcept;
|
||||
|
||||
[[nodiscard]] int GenericAttributeIndex(Attribute attribute);
|
||||
|
||||
[[nodiscard]] std::string NameOf(Attribute attribute);
|
||||
|
||||
} // namespace Shader::IR
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Shader::IR::Attribute> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const Shader::IR::Attribute& attribute, FormatContext& ctx) {
|
||||
return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(attribute));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "common/bit_cast.h"
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/value.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
Block::Block(u32 begin, u32 end) : location_begin{begin}, location_end{end} {}
|
||||
|
||||
Block::~Block() = default;
|
||||
|
||||
void Block::AppendNewInst(Opcode op, std::initializer_list<Value> args) {
|
||||
PrependNewInst(end(), op, args);
|
||||
}
|
||||
|
||||
Block::iterator Block::PrependNewInst(iterator insertion_point, Opcode op,
|
||||
std::initializer_list<Value> args) {
|
||||
Inst* const inst{std::construct_at(instruction_alloc_pool.allocate(), op)};
|
||||
const auto result_it{instructions.insert(insertion_point, *inst)};
|
||||
|
||||
if (inst->NumArgs() != args.size()) {
|
||||
throw InvalidArgument("Invalid number of arguments {} in {}", args.size(), op);
|
||||
}
|
||||
std::ranges::for_each(args, [inst, index = size_t{0}](const Value& arg) mutable {
|
||||
inst->SetArg(index, arg);
|
||||
++index;
|
||||
});
|
||||
return result_it;
|
||||
}
|
||||
|
||||
u32 Block::LocationBegin() const noexcept {
|
||||
return location_begin;
|
||||
}
|
||||
|
||||
u32 Block::LocationEnd() const noexcept {
|
||||
return location_end;
|
||||
}
|
||||
|
||||
Block::InstructionList& Block::Instructions() noexcept {
|
||||
return instructions;
|
||||
}
|
||||
|
||||
const Block::InstructionList& Block::Instructions() const noexcept {
|
||||
return instructions;
|
||||
}
|
||||
|
||||
static std::string ArgToIndex(const std::map<const Block*, size_t>& block_to_index,
|
||||
const std::map<const Inst*, size_t>& inst_to_index,
|
||||
const Value& arg) {
|
||||
if (arg.IsEmpty()) {
|
||||
return "<null>";
|
||||
}
|
||||
if (arg.IsLabel()) {
|
||||
if (const auto it{block_to_index.find(arg.Label())}; it != block_to_index.end()) {
|
||||
return fmt::format("{{Block ${}}}", it->second);
|
||||
}
|
||||
return fmt::format("$<unknown block {:016x}>", reinterpret_cast<u64>(arg.Label()));
|
||||
}
|
||||
if (!arg.IsImmediate()) {
|
||||
if (const auto it{inst_to_index.find(arg.Inst())}; it != inst_to_index.end()) {
|
||||
return fmt::format("%{}", it->second);
|
||||
}
|
||||
return fmt::format("%<unknown inst {:016x}>", reinterpret_cast<u64>(arg.Inst()));
|
||||
}
|
||||
switch (arg.Type()) {
|
||||
case Type::U1:
|
||||
return fmt::format("#{}", arg.U1() ? '1' : '0');
|
||||
case Type::U8:
|
||||
return fmt::format("#{}", arg.U8());
|
||||
case Type::U16:
|
||||
return fmt::format("#{}", arg.U16());
|
||||
case Type::U32:
|
||||
return fmt::format("#{}", arg.U32());
|
||||
case Type::U64:
|
||||
return fmt::format("#{}", arg.U64());
|
||||
case Type::Reg:
|
||||
return fmt::format("{}", arg.Reg());
|
||||
case Type::Pred:
|
||||
return fmt::format("{}", arg.Pred());
|
||||
case Type::Attribute:
|
||||
return fmt::format("{}", arg.Attribute());
|
||||
default:
|
||||
return "<unknown immediate type>";
|
||||
}
|
||||
}
|
||||
|
||||
std::string DumpBlock(const Block& block) {
|
||||
size_t inst_index{0};
|
||||
std::map<const Inst*, size_t> inst_to_index;
|
||||
return DumpBlock(block, {}, inst_to_index, inst_index);
|
||||
}
|
||||
|
||||
std::string DumpBlock(const Block& block, const std::map<const Block*, size_t>& block_to_index,
|
||||
std::map<const Inst*, size_t>& inst_to_index, size_t& inst_index) {
|
||||
std::string ret{"Block"};
|
||||
if (const auto it{block_to_index.find(&block)}; it != block_to_index.end()) {
|
||||
ret += fmt::format(" ${}", it->second);
|
||||
}
|
||||
ret += fmt::format(": begin={:04x} end={:04x}\n", block.LocationBegin(), block.LocationEnd());
|
||||
|
||||
for (const Inst& inst : block) {
|
||||
const Opcode op{inst.Opcode()};
|
||||
ret += fmt::format("[{:016x}] ", reinterpret_cast<u64>(&inst));
|
||||
if (TypeOf(op) != Type::Void) {
|
||||
ret += fmt::format("%{:<5} = {}", inst_index, op);
|
||||
} else {
|
||||
ret += fmt::format(" {}", op); // '%00000 = ' -> 1 + 5 + 3 = 9 spaces
|
||||
}
|
||||
const size_t arg_count{NumArgsOf(op)};
|
||||
for (size_t arg_index = 0; arg_index < arg_count; ++arg_index) {
|
||||
const Value arg{inst.Arg(arg_index)};
|
||||
ret += arg_index != 0 ? ", " : " ";
|
||||
ret += ArgToIndex(block_to_index, inst_to_index, arg);
|
||||
|
||||
const Type actual_type{arg.Type()};
|
||||
const Type expected_type{ArgTypeOf(op, arg_index)};
|
||||
if (!AreTypesCompatible(actual_type, expected_type)) {
|
||||
ret += fmt::format("<type error: {} != {}>", actual_type, expected_type);
|
||||
}
|
||||
}
|
||||
if (TypeOf(op) != Type::Void) {
|
||||
ret += fmt::format(" (uses: {})\n", inst.UseCount());
|
||||
} else {
|
||||
ret += '\n';
|
||||
}
|
||||
|
||||
inst_to_index.emplace(&inst, inst_index);
|
||||
++inst_index;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,134 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include <boost/pool/pool_alloc.hpp>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/microinstruction.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
class Block {
|
||||
public:
|
||||
using InstructionList = boost::intrusive::list<Inst>;
|
||||
using size_type = InstructionList::size_type;
|
||||
using iterator = InstructionList::iterator;
|
||||
using const_iterator = InstructionList::const_iterator;
|
||||
using reverse_iterator = InstructionList::reverse_iterator;
|
||||
using const_reverse_iterator = InstructionList::const_reverse_iterator;
|
||||
|
||||
explicit Block(u32 begin, u32 end);
|
||||
~Block();
|
||||
|
||||
Block(const Block&) = delete;
|
||||
Block& operator=(const Block&) = delete;
|
||||
|
||||
Block(Block&&) = default;
|
||||
Block& operator=(Block&&) = default;
|
||||
|
||||
/// Appends a new instruction to the end of this basic block.
|
||||
void AppendNewInst(Opcode op, std::initializer_list<Value> args);
|
||||
|
||||
/// Prepends a new instruction to this basic block before the insertion point.
|
||||
iterator PrependNewInst(iterator insertion_point, Opcode op, std::initializer_list<Value> args);
|
||||
|
||||
/// Gets the starting location of this basic block.
|
||||
[[nodiscard]] u32 LocationBegin() const noexcept;
|
||||
/// Gets the end location for this basic block.
|
||||
[[nodiscard]] u32 LocationEnd() const noexcept;
|
||||
|
||||
/// Gets a mutable reference to the instruction list for this basic block.
|
||||
InstructionList& Instructions() noexcept;
|
||||
/// Gets an immutable reference to the instruction list for this basic block.
|
||||
const InstructionList& Instructions() const noexcept;
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return instructions.empty();
|
||||
}
|
||||
[[nodiscard]] size_type size() const {
|
||||
return instructions.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] Inst& front() {
|
||||
return instructions.front();
|
||||
}
|
||||
[[nodiscard]] const Inst& front() const {
|
||||
return instructions.front();
|
||||
}
|
||||
|
||||
[[nodiscard]] Inst& back() {
|
||||
return instructions.back();
|
||||
}
|
||||
[[nodiscard]] const Inst& back() const {
|
||||
return instructions.back();
|
||||
}
|
||||
|
||||
[[nodiscard]] iterator begin() {
|
||||
return instructions.begin();
|
||||
}
|
||||
[[nodiscard]] const_iterator begin() const {
|
||||
return instructions.begin();
|
||||
}
|
||||
[[nodiscard]] iterator end() {
|
||||
return instructions.end();
|
||||
}
|
||||
[[nodiscard]] const_iterator end() const {
|
||||
return instructions.end();
|
||||
}
|
||||
|
||||
[[nodiscard]] reverse_iterator rbegin() {
|
||||
return instructions.rbegin();
|
||||
}
|
||||
[[nodiscard]] const_reverse_iterator rbegin() const {
|
||||
return instructions.rbegin();
|
||||
}
|
||||
[[nodiscard]] reverse_iterator rend() {
|
||||
return instructions.rend();
|
||||
}
|
||||
[[nodiscard]] const_reverse_iterator rend() const {
|
||||
return instructions.rend();
|
||||
}
|
||||
|
||||
[[nodiscard]] const_iterator cbegin() const {
|
||||
return instructions.cbegin();
|
||||
}
|
||||
[[nodiscard]] const_iterator cend() const {
|
||||
return instructions.cend();
|
||||
}
|
||||
|
||||
[[nodiscard]] const_reverse_iterator crbegin() const {
|
||||
return instructions.crbegin();
|
||||
}
|
||||
[[nodiscard]] const_reverse_iterator crend() const {
|
||||
return instructions.crend();
|
||||
}
|
||||
|
||||
private:
|
||||
/// Starting location of this block
|
||||
u32 location_begin;
|
||||
/// End location of this block
|
||||
u32 location_end;
|
||||
|
||||
/// List of instructions in this block.
|
||||
InstructionList instructions;
|
||||
|
||||
/// Memory pool for instruction list
|
||||
boost::fast_pool_allocator<Inst, boost::default_user_allocator_malloc_free,
|
||||
boost::details::pool::null_mutex>
|
||||
instruction_alloc_pool;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::string DumpBlock(const Block& block);
|
||||
|
||||
[[nodiscard]] std::string DumpBlock(const Block& block,
|
||||
const std::map<const Block*, size_t>& block_to_index,
|
||||
std::map<const Inst*, size_t>& inst_to_index,
|
||||
size_t& inst_index);
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/condition.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
std::string NameOf(Condition condition) {
|
||||
std::string ret;
|
||||
if (condition.FlowTest() != FlowTest::T) {
|
||||
ret = fmt::to_string(condition.FlowTest());
|
||||
}
|
||||
const auto [pred, negated]{condition.Pred()};
|
||||
if (pred != Pred::PT || negated) {
|
||||
if (!ret.empty()) {
|
||||
ret += '&';
|
||||
}
|
||||
if (negated) {
|
||||
ret += '!';
|
||||
}
|
||||
ret += fmt::to_string(pred);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <compare>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/frontend/ir/flow_test.h"
|
||||
#include "shader_recompiler/frontend/ir/pred.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
class Condition {
|
||||
public:
|
||||
Condition() noexcept = default;
|
||||
|
||||
explicit Condition(FlowTest flow_test_, Pred pred_, bool pred_negated_ = false) noexcept
|
||||
: flow_test{static_cast<u16>(flow_test_)}, pred{static_cast<u8>(pred_)},
|
||||
pred_negated{pred_negated_ ? u8{1} : u8{0}} {}
|
||||
|
||||
explicit Condition(Pred pred_, bool pred_negated_ = false) noexcept
|
||||
: Condition(FlowTest::T, pred_, pred_negated_) {}
|
||||
|
||||
Condition(bool value) : Condition(Pred::PT, !value) {}
|
||||
|
||||
auto operator<=>(const Condition&) const noexcept = default;
|
||||
|
||||
[[nodiscard]] IR::FlowTest FlowTest() const noexcept {
|
||||
return static_cast<IR::FlowTest>(flow_test);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::pair<IR::Pred, bool> Pred() const noexcept {
|
||||
return {static_cast<IR::Pred>(pred), pred_negated != 0};
|
||||
}
|
||||
|
||||
private:
|
||||
u16 flow_test;
|
||||
u8 pred;
|
||||
u8 pred_negated;
|
||||
};
|
||||
|
||||
std::string NameOf(Condition condition);
|
||||
|
||||
} // namespace Shader::IR
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Shader::IR::Condition> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const Shader::IR::Condition& cond, FormatContext& ctx) {
|
||||
return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(cond));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/flow_test.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
std::string NameOf(FlowTest flow_test) {
|
||||
switch (flow_test) {
|
||||
case FlowTest::F:
|
||||
return "F";
|
||||
case FlowTest::LT:
|
||||
return "LT";
|
||||
case FlowTest::EQ:
|
||||
return "EQ";
|
||||
case FlowTest::LE:
|
||||
return "LE";
|
||||
case FlowTest::GT:
|
||||
return "GT";
|
||||
case FlowTest::NE:
|
||||
return "NE";
|
||||
case FlowTest::GE:
|
||||
return "GE";
|
||||
case FlowTest::NUM:
|
||||
return "NUM";
|
||||
case FlowTest::NaN:
|
||||
return "NAN";
|
||||
case FlowTest::LTU:
|
||||
return "LTU";
|
||||
case FlowTest::EQU:
|
||||
return "EQU";
|
||||
case FlowTest::LEU:
|
||||
return "LEU";
|
||||
case FlowTest::GTU:
|
||||
return "GTU";
|
||||
case FlowTest::NEU:
|
||||
return "NEU";
|
||||
case FlowTest::GEU:
|
||||
return "GEU";
|
||||
case FlowTest::T:
|
||||
return "T";
|
||||
case FlowTest::OFF:
|
||||
return "OFF";
|
||||
case FlowTest::LO:
|
||||
return "LO";
|
||||
case FlowTest::SFF:
|
||||
return "SFF";
|
||||
case FlowTest::LS:
|
||||
return "LS";
|
||||
case FlowTest::HI:
|
||||
return "HI";
|
||||
case FlowTest::SFT:
|
||||
return "SFT";
|
||||
case FlowTest::HS:
|
||||
return "HS";
|
||||
case FlowTest::OFT:
|
||||
return "OFT";
|
||||
case FlowTest::CSM_TA:
|
||||
return "CSM_TA";
|
||||
case FlowTest::CSM_TR:
|
||||
return "CSM_TR";
|
||||
case FlowTest::CSM_MX:
|
||||
return "CSM_MX";
|
||||
case FlowTest::FCSM_TA:
|
||||
return "FCSM_TA";
|
||||
case FlowTest::FCSM_TR:
|
||||
return "FCSM_TR";
|
||||
case FlowTest::FCSM_MX:
|
||||
return "FCSM_MX";
|
||||
case FlowTest::RLE:
|
||||
return "RLE";
|
||||
case FlowTest::RGT:
|
||||
return "RGT";
|
||||
}
|
||||
return fmt::format("<invalid flow test {}>", static_cast<int>(flow_test));
|
||||
}
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
enum class FlowTest {
|
||||
F,
|
||||
LT,
|
||||
EQ,
|
||||
LE,
|
||||
GT,
|
||||
NE,
|
||||
GE,
|
||||
NUM,
|
||||
NaN,
|
||||
LTU,
|
||||
EQU,
|
||||
LEU,
|
||||
GTU,
|
||||
NEU,
|
||||
GEU,
|
||||
T,
|
||||
OFF,
|
||||
LO,
|
||||
SFF,
|
||||
LS,
|
||||
HI,
|
||||
SFT,
|
||||
HS,
|
||||
OFT,
|
||||
CSM_TA,
|
||||
CSM_TR,
|
||||
CSM_MX,
|
||||
FCSM_TA,
|
||||
FCSM_TR,
|
||||
FCSM_MX,
|
||||
RLE,
|
||||
RGT,
|
||||
};
|
||||
|
||||
[[nodiscard]] std::string NameOf(FlowTest flow_test);
|
||||
|
||||
} // namespace Shader::IR
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Shader::IR::FlowTest> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const Shader::IR::FlowTest& flow_test, FormatContext& ctx) {
|
||||
return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(flow_test));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,533 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/bit_cast.h"
|
||||
#include "shader_recompiler/frontend/ir/ir_emitter.h"
|
||||
#include "shader_recompiler/frontend/ir/value.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
[[noreturn]] static void ThrowInvalidType(Type type) {
|
||||
throw InvalidArgument("Invalid type {}", type);
|
||||
}
|
||||
|
||||
U1 IREmitter::Imm1(bool value) const {
|
||||
return U1{Value{value}};
|
||||
}
|
||||
|
||||
U8 IREmitter::Imm8(u8 value) const {
|
||||
return U8{Value{value}};
|
||||
}
|
||||
|
||||
U16 IREmitter::Imm16(u16 value) const {
|
||||
return U16{Value{value}};
|
||||
}
|
||||
|
||||
U32 IREmitter::Imm32(u32 value) const {
|
||||
return U32{Value{value}};
|
||||
}
|
||||
|
||||
U32 IREmitter::Imm32(s32 value) const {
|
||||
return U32{Value{static_cast<u32>(value)}};
|
||||
}
|
||||
|
||||
U32 IREmitter::Imm32(f32 value) const {
|
||||
return U32{Value{Common::BitCast<u32>(value)}};
|
||||
}
|
||||
|
||||
U64 IREmitter::Imm64(u64 value) const {
|
||||
return U64{Value{value}};
|
||||
}
|
||||
|
||||
U64 IREmitter::Imm64(f64 value) const {
|
||||
return U64{Value{Common::BitCast<u64>(value)}};
|
||||
}
|
||||
|
||||
void IREmitter::Branch(IR::Block* label) {
|
||||
Inst(Opcode::Branch, label);
|
||||
}
|
||||
|
||||
void IREmitter::BranchConditional(const U1& cond, IR::Block* true_label, IR::Block* false_label) {
|
||||
Inst(Opcode::BranchConditional, cond, true_label, false_label);
|
||||
}
|
||||
|
||||
void IREmitter::Exit() {
|
||||
Inst(Opcode::Exit);
|
||||
}
|
||||
|
||||
void IREmitter::Return() {
|
||||
Inst(Opcode::Return);
|
||||
}
|
||||
|
||||
void IREmitter::Unreachable() {
|
||||
Inst(Opcode::Unreachable);
|
||||
}
|
||||
|
||||
U32 IREmitter::GetReg(IR::Reg reg) {
|
||||
return Inst<U32>(Opcode::GetRegister, reg);
|
||||
}
|
||||
|
||||
void IREmitter::SetReg(IR::Reg reg, const U32& value) {
|
||||
Inst(Opcode::SetRegister, reg, value);
|
||||
}
|
||||
|
||||
U1 IREmitter::GetPred(IR::Pred pred, bool is_negated) {
|
||||
const U1 value{Inst<U1>(Opcode::GetPred, pred)};
|
||||
if (is_negated) {
|
||||
return Inst<U1>(Opcode::LogicalNot, value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
void IREmitter::SetPred(IR::Pred pred, const U1& value) {
|
||||
Inst(Opcode::SetPred, pred, value);
|
||||
}
|
||||
|
||||
U32 IREmitter::GetCbuf(const U32& binding, const U32& byte_offset) {
|
||||
return Inst<U32>(Opcode::GetCbuf, binding, byte_offset);
|
||||
}
|
||||
|
||||
U1 IREmitter::GetZFlag() {
|
||||
return Inst<U1>(Opcode::GetZFlag);
|
||||
}
|
||||
|
||||
U1 IREmitter::GetSFlag() {
|
||||
return Inst<U1>(Opcode::GetSFlag);
|
||||
}
|
||||
|
||||
U1 IREmitter::GetCFlag() {
|
||||
return Inst<U1>(Opcode::GetCFlag);
|
||||
}
|
||||
|
||||
U1 IREmitter::GetOFlag() {
|
||||
return Inst<U1>(Opcode::GetOFlag);
|
||||
}
|
||||
|
||||
void IREmitter::SetZFlag(const U1& value) {
|
||||
Inst(Opcode::SetZFlag, value);
|
||||
}
|
||||
|
||||
void IREmitter::SetSFlag(const U1& value) {
|
||||
Inst(Opcode::SetSFlag, value);
|
||||
}
|
||||
|
||||
void IREmitter::SetCFlag(const U1& value) {
|
||||
Inst(Opcode::SetCFlag, value);
|
||||
}
|
||||
|
||||
void IREmitter::SetOFlag(const U1& value) {
|
||||
Inst(Opcode::SetOFlag, value);
|
||||
}
|
||||
|
||||
U32 IREmitter::GetAttribute(IR::Attribute attribute) {
|
||||
return Inst<U32>(Opcode::GetAttribute, attribute);
|
||||
}
|
||||
|
||||
void IREmitter::SetAttribute(IR::Attribute attribute, const U32& value) {
|
||||
Inst(Opcode::SetAttribute, attribute, value);
|
||||
}
|
||||
|
||||
void IREmitter::WriteGlobalU8(const U64& address, const U32& value) {
|
||||
Inst(Opcode::WriteGlobalU8, address, value);
|
||||
}
|
||||
|
||||
void IREmitter::WriteGlobalS8(const U64& address, const U32& value) {
|
||||
Inst(Opcode::WriteGlobalS8, address, value);
|
||||
}
|
||||
|
||||
void IREmitter::WriteGlobalU16(const U64& address, const U32& value) {
|
||||
Inst(Opcode::WriteGlobalU16, address, value);
|
||||
}
|
||||
|
||||
void IREmitter::WriteGlobalS16(const U64& address, const U32& value) {
|
||||
Inst(Opcode::WriteGlobalS16, address, value);
|
||||
}
|
||||
|
||||
void IREmitter::WriteGlobal32(const U64& address, const U32& value) {
|
||||
Inst(Opcode::WriteGlobal32, address, value);
|
||||
}
|
||||
|
||||
void IREmitter::WriteGlobal64(const U64& address, const IR::Value& vector) {
|
||||
Inst(Opcode::WriteGlobal64, address, vector);
|
||||
}
|
||||
|
||||
void IREmitter::WriteGlobal128(const U64& address, const IR::Value& vector) {
|
||||
Inst(Opcode::WriteGlobal128, address, vector);
|
||||
}
|
||||
|
||||
U1 IREmitter::GetZeroFromOp(const Value& op) {
|
||||
return Inst<U1>(Opcode::GetZeroFromOp, op);
|
||||
}
|
||||
|
||||
U1 IREmitter::GetSignFromOp(const Value& op) {
|
||||
return Inst<U1>(Opcode::GetSignFromOp, op);
|
||||
}
|
||||
|
||||
U1 IREmitter::GetCarryFromOp(const Value& op) {
|
||||
return Inst<U1>(Opcode::GetCarryFromOp, op);
|
||||
}
|
||||
|
||||
U1 IREmitter::GetOverflowFromOp(const Value& op) {
|
||||
return Inst<U1>(Opcode::GetOverflowFromOp, op);
|
||||
}
|
||||
|
||||
U16U32U64 IREmitter::FPAdd(const U16U32U64& a, const U16U32U64& b) {
|
||||
if (a.Type() != a.Type()) {
|
||||
throw InvalidArgument("Mismatching types {} and {}", a.Type(), b.Type());
|
||||
}
|
||||
switch (a.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U16>(Opcode::FPAdd16, a, b);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::FPAdd32, a, b);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::FPAdd64, a, b);
|
||||
default:
|
||||
ThrowInvalidType(a.Type());
|
||||
}
|
||||
}
|
||||
|
||||
Value IREmitter::CompositeConstruct(const UAny& e1, const UAny& e2) {
|
||||
if (e1.Type() != e2.Type()) {
|
||||
throw InvalidArgument("Incompatible types {} {}", e1.Type(), e2.Type());
|
||||
}
|
||||
return Inst(Opcode::CompositeConstruct2, e1, e2);
|
||||
}
|
||||
|
||||
Value IREmitter::CompositeConstruct(const UAny& e1, const UAny& e2, const UAny& e3) {
|
||||
if (e1.Type() != e2.Type() || e1.Type() != e3.Type()) {
|
||||
throw InvalidArgument("Incompatible types {} {} {}", e1.Type(), e2.Type(), e3.Type());
|
||||
}
|
||||
return Inst(Opcode::CompositeConstruct3, e1, e2, e3);
|
||||
}
|
||||
|
||||
Value IREmitter::CompositeConstruct(const UAny& e1, const UAny& e2, const UAny& e3,
|
||||
const UAny& e4) {
|
||||
if (e1.Type() != e2.Type() || e1.Type() != e3.Type() || e1.Type() != e4.Type()) {
|
||||
throw InvalidArgument("Incompatible types {} {} {}", e1.Type(), e2.Type(), e3.Type(),
|
||||
e4.Type());
|
||||
}
|
||||
return Inst(Opcode::CompositeConstruct4, e1, e2, e3, e4);
|
||||
}
|
||||
|
||||
UAny IREmitter::CompositeExtract(const Value& vector, size_t element) {
|
||||
if (element >= 4) {
|
||||
throw InvalidArgument("Out of bounds element {}", element);
|
||||
}
|
||||
return Inst<UAny>(Opcode::CompositeExtract, vector, Imm32(static_cast<u32>(element)));
|
||||
}
|
||||
|
||||
U64 IREmitter::PackUint2x32(const Value& vector) {
|
||||
return Inst<U64>(Opcode::PackUint2x32, vector);
|
||||
}
|
||||
|
||||
Value IREmitter::UnpackUint2x32(const U64& value) {
|
||||
return Inst<Value>(Opcode::UnpackUint2x32, value);
|
||||
}
|
||||
|
||||
U32 IREmitter::PackFloat2x16(const Value& vector) {
|
||||
return Inst<U32>(Opcode::PackFloat2x16, vector);
|
||||
}
|
||||
|
||||
Value IREmitter::UnpackFloat2x16(const U32& value) {
|
||||
return Inst<Value>(Opcode::UnpackFloat2x16, value);
|
||||
}
|
||||
|
||||
U64 IREmitter::PackDouble2x32(const Value& vector) {
|
||||
return Inst<U64>(Opcode::PackDouble2x32, vector);
|
||||
}
|
||||
|
||||
Value IREmitter::UnpackDouble2x32(const U64& value) {
|
||||
return Inst<Value>(Opcode::UnpackDouble2x32, value);
|
||||
}
|
||||
|
||||
U16U32U64 IREmitter::FPMul(const U16U32U64& a, const U16U32U64& b) {
|
||||
if (a.Type() != b.Type()) {
|
||||
throw InvalidArgument("Mismatching types {} and {}", a.Type(), b.Type());
|
||||
}
|
||||
switch (a.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U16>(Opcode::FPMul16, a, b);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::FPMul32, a, b);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::FPMul64, a, b);
|
||||
default:
|
||||
ThrowInvalidType(a.Type());
|
||||
}
|
||||
}
|
||||
|
||||
U16U32U64 IREmitter::FPAbs(const U16U32U64& value) {
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U16>(Opcode::FPAbs16, value);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::FPAbs32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::FPAbs64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
U16U32U64 IREmitter::FPNeg(const U16U32U64& value) {
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U16>(Opcode::FPNeg16, value);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::FPNeg32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::FPNeg64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
U16U32U64 IREmitter::FPAbsNeg(const U16U32U64& value, bool abs, bool neg) {
|
||||
U16U32U64 result{value};
|
||||
if (abs) {
|
||||
result = FPAbs(value);
|
||||
}
|
||||
if (neg) {
|
||||
result = FPNeg(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
U32 IREmitter::FPCosNotReduced(const U32& value) {
|
||||
return Inst<U32>(Opcode::FPCosNotReduced, value);
|
||||
}
|
||||
|
||||
U32 IREmitter::FPExp2NotReduced(const U32& value) {
|
||||
return Inst<U32>(Opcode::FPExp2NotReduced, value);
|
||||
}
|
||||
|
||||
U32 IREmitter::FPLog2(const U32& value) {
|
||||
return Inst<U32>(Opcode::FPLog2, value);
|
||||
}
|
||||
|
||||
U32U64 IREmitter::FPRecip(const U32U64& value) {
|
||||
switch (value.Type()) {
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::FPRecip32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::FPRecip64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
U32U64 IREmitter::FPRecipSqrt(const U32U64& value) {
|
||||
switch (value.Type()) {
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::FPRecipSqrt32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::FPRecipSqrt64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
U32 IREmitter::FPSinNotReduced(const U32& value) {
|
||||
return Inst<U32>(Opcode::FPSinNotReduced, value);
|
||||
}
|
||||
|
||||
U32 IREmitter::FPSqrt(const U32& value) {
|
||||
return Inst<U32>(Opcode::FPSqrt, value);
|
||||
}
|
||||
|
||||
U16U32U64 IREmitter::FPSaturate(const U16U32U64& value) {
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U16>(Opcode::FPSaturate16, value);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::FPSaturate32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::FPSaturate64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
U16U32U64 IREmitter::FPRoundEven(const U16U32U64& value) {
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U16>(Opcode::FPRoundEven16, value);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::FPRoundEven32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::FPRoundEven64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
U16U32U64 IREmitter::FPFloor(const U16U32U64& value) {
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U16>(Opcode::FPFloor16, value);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::FPFloor32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::FPFloor64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
U16U32U64 IREmitter::FPCeil(const U16U32U64& value) {
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U16>(Opcode::FPCeil16, value);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::FPCeil32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::FPCeil64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
U16U32U64 IREmitter::FPTrunc(const U16U32U64& value) {
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U16>(Opcode::FPTrunc16, value);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::FPTrunc32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::FPTrunc64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
U1 IREmitter::LogicalOr(const U1& a, const U1& b) {
|
||||
return Inst<U1>(Opcode::LogicalOr, a, b);
|
||||
}
|
||||
|
||||
U1 IREmitter::LogicalAnd(const U1& a, const U1& b) {
|
||||
return Inst<U1>(Opcode::LogicalAnd, a, b);
|
||||
}
|
||||
|
||||
U1 IREmitter::LogicalNot(const U1& value) {
|
||||
return Inst<U1>(Opcode::LogicalNot, value);
|
||||
}
|
||||
|
||||
U32U64 IREmitter::ConvertFToS(size_t bitsize, const U16U32U64& value) {
|
||||
switch (bitsize) {
|
||||
case 16:
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U32>(Opcode::ConvertS16F16, value);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::ConvertS16F32, value);
|
||||
case Type::U64:
|
||||
return Inst<U32>(Opcode::ConvertS16F64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
case 32:
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U32>(Opcode::ConvertS32F16, value);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::ConvertS32F32, value);
|
||||
case Type::U64:
|
||||
return Inst<U32>(Opcode::ConvertS32F64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
case 64:
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U64>(Opcode::ConvertS64F16, value);
|
||||
case Type::U32:
|
||||
return Inst<U64>(Opcode::ConvertS64F32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::ConvertS64F64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
default:
|
||||
throw InvalidArgument("Invalid destination bitsize {}", bitsize);
|
||||
}
|
||||
}
|
||||
|
||||
U32U64 IREmitter::ConvertFToU(size_t bitsize, const U16U32U64& value) {
|
||||
switch (bitsize) {
|
||||
case 16:
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U32>(Opcode::ConvertU16F16, value);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::ConvertU16F32, value);
|
||||
case Type::U64:
|
||||
return Inst<U32>(Opcode::ConvertU16F64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
case 32:
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U32>(Opcode::ConvertU32F16, value);
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::ConvertU32F32, value);
|
||||
case Type::U64:
|
||||
return Inst<U32>(Opcode::ConvertU32F64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
case 64:
|
||||
switch (value.Type()) {
|
||||
case Type::U16:
|
||||
return Inst<U64>(Opcode::ConvertU64F16, value);
|
||||
case Type::U32:
|
||||
return Inst<U64>(Opcode::ConvertU64F32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::ConvertU64F64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
default:
|
||||
throw InvalidArgument("Invalid destination bitsize {}", bitsize);
|
||||
}
|
||||
}
|
||||
|
||||
U32U64 IREmitter::ConvertFToI(size_t bitsize, bool is_signed, const U16U32U64& value) {
|
||||
if (is_signed) {
|
||||
return ConvertFToS(bitsize, value);
|
||||
} else {
|
||||
return ConvertFToU(bitsize, value);
|
||||
}
|
||||
}
|
||||
|
||||
U32U64 IREmitter::ConvertU(size_t bitsize, const U32U64& value) {
|
||||
switch (bitsize) {
|
||||
case 32:
|
||||
switch (value.Type()) {
|
||||
case Type::U32:
|
||||
// Nothing to do
|
||||
return value;
|
||||
case Type::U64:
|
||||
return Inst<U32>(Opcode::ConvertU32U64, value);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 64:
|
||||
switch (value.Type()) {
|
||||
case Type::U32:
|
||||
// Nothing to do
|
||||
return value;
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::ConvertU64U32, value);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw NotImplementedException("Conversion from {} to {} bits", value.Type(), bitsize);
|
||||
}
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,123 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_recompiler/frontend/ir/attribute.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/value.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
class IREmitter {
|
||||
public:
|
||||
explicit IREmitter(Block& block_) : block{block_}, insertion_point{block.end()} {}
|
||||
|
||||
Block& block;
|
||||
|
||||
[[nodiscard]] U1 Imm1(bool value) const;
|
||||
[[nodiscard]] U8 Imm8(u8 value) const;
|
||||
[[nodiscard]] U16 Imm16(u16 value) const;
|
||||
[[nodiscard]] U32 Imm32(u32 value) const;
|
||||
[[nodiscard]] U32 Imm32(s32 value) const;
|
||||
[[nodiscard]] U32 Imm32(f32 value) const;
|
||||
[[nodiscard]] U64 Imm64(u64 value) const;
|
||||
[[nodiscard]] U64 Imm64(f64 value) const;
|
||||
|
||||
void Branch(IR::Block* label);
|
||||
void BranchConditional(const U1& cond, IR::Block* true_label, IR::Block* false_label);
|
||||
void Exit();
|
||||
void Return();
|
||||
void Unreachable();
|
||||
|
||||
[[nodiscard]] U32 GetReg(IR::Reg reg);
|
||||
void SetReg(IR::Reg reg, const U32& value);
|
||||
|
||||
[[nodiscard]] U1 GetPred(IR::Pred pred, bool is_negated = false);
|
||||
void SetPred(IR::Pred pred, const U1& value);
|
||||
|
||||
[[nodiscard]] U32 GetCbuf(const U32& binding, const U32& byte_offset);
|
||||
|
||||
[[nodiscard]] U1 GetZFlag();
|
||||
[[nodiscard]] U1 GetSFlag();
|
||||
[[nodiscard]] U1 GetCFlag();
|
||||
[[nodiscard]] U1 GetOFlag();
|
||||
|
||||
void SetZFlag(const U1& value);
|
||||
void SetSFlag(const U1& value);
|
||||
void SetCFlag(const U1& value);
|
||||
void SetOFlag(const U1& value);
|
||||
|
||||
[[nodiscard]] U32 GetAttribute(IR::Attribute attribute);
|
||||
void SetAttribute(IR::Attribute attribute, const U32& value);
|
||||
|
||||
void WriteGlobalU8(const U64& address, const U32& value);
|
||||
void WriteGlobalS8(const U64& address, const U32& value);
|
||||
void WriteGlobalU16(const U64& address, const U32& value);
|
||||
void WriteGlobalS16(const U64& address, const U32& value);
|
||||
void WriteGlobal32(const U64& address, const U32& value);
|
||||
void WriteGlobal64(const U64& address, const IR::Value& vector);
|
||||
void WriteGlobal128(const U64& address, const IR::Value& vector);
|
||||
|
||||
[[nodiscard]] U1 GetZeroFromOp(const Value& op);
|
||||
[[nodiscard]] U1 GetSignFromOp(const Value& op);
|
||||
[[nodiscard]] U1 GetCarryFromOp(const Value& op);
|
||||
[[nodiscard]] U1 GetOverflowFromOp(const Value& op);
|
||||
|
||||
[[nodiscard]] Value CompositeConstruct(const UAny& e1, const UAny& e2);
|
||||
[[nodiscard]] Value CompositeConstruct(const UAny& e1, const UAny& e2, const UAny& e3);
|
||||
[[nodiscard]] Value CompositeConstruct(const UAny& e1, const UAny& e2, const UAny& e3,
|
||||
const UAny& e4);
|
||||
[[nodiscard]] UAny CompositeExtract(const Value& vector, size_t element);
|
||||
|
||||
[[nodiscard]] U64 PackUint2x32(const Value& vector);
|
||||
[[nodiscard]] Value UnpackUint2x32(const U64& value);
|
||||
|
||||
[[nodiscard]] U32 PackFloat2x16(const Value& vector);
|
||||
[[nodiscard]] Value UnpackFloat2x16(const U32& value);
|
||||
|
||||
[[nodiscard]] U64 PackDouble2x32(const Value& vector);
|
||||
[[nodiscard]] Value UnpackDouble2x32(const U64& value);
|
||||
|
||||
[[nodiscard]] U16U32U64 FPAdd(const U16U32U64& a, const U16U32U64& b);
|
||||
[[nodiscard]] U16U32U64 FPMul(const U16U32U64& a, const U16U32U64& b);
|
||||
|
||||
[[nodiscard]] U16U32U64 FPAbs(const U16U32U64& value);
|
||||
[[nodiscard]] U16U32U64 FPNeg(const U16U32U64& value);
|
||||
[[nodiscard]] U16U32U64 FPAbsNeg(const U16U32U64& value, bool abs, bool neg);
|
||||
|
||||
[[nodiscard]] U32 FPCosNotReduced(const U32& value);
|
||||
[[nodiscard]] U32 FPExp2NotReduced(const U32& value);
|
||||
[[nodiscard]] U32 FPLog2(const U32& value);
|
||||
[[nodiscard]] U32U64 FPRecip(const U32U64& value);
|
||||
[[nodiscard]] U32U64 FPRecipSqrt(const U32U64& value);
|
||||
[[nodiscard]] U32 FPSinNotReduced(const U32& value);
|
||||
[[nodiscard]] U32 FPSqrt(const U32& value);
|
||||
[[nodiscard]] U16U32U64 FPSaturate(const U16U32U64& value);
|
||||
[[nodiscard]] U16U32U64 FPRoundEven(const U16U32U64& value);
|
||||
[[nodiscard]] U16U32U64 FPFloor(const U16U32U64& value);
|
||||
[[nodiscard]] U16U32U64 FPCeil(const U16U32U64& value);
|
||||
[[nodiscard]] U16U32U64 FPTrunc(const U16U32U64& value);
|
||||
|
||||
[[nodiscard]] U1 LogicalOr(const U1& a, const U1& b);
|
||||
[[nodiscard]] U1 LogicalAnd(const U1& a, const U1& b);
|
||||
[[nodiscard]] U1 LogicalNot(const U1& value);
|
||||
|
||||
[[nodiscard]] U32U64 ConvertFToS(size_t bitsize, const U16U32U64& value);
|
||||
[[nodiscard]] U32U64 ConvertFToU(size_t bitsize, const U16U32U64& value);
|
||||
[[nodiscard]] U32U64 ConvertFToI(size_t bitsize, bool is_signed, const U16U32U64& value);
|
||||
|
||||
[[nodiscard]] U32U64 ConvertU(size_t bitsize, const U32U64& value);
|
||||
|
||||
private:
|
||||
IR::Block::iterator insertion_point;
|
||||
|
||||
template <typename T = Value, typename... Args>
|
||||
T Inst(Opcode op, Args... args) {
|
||||
auto it{block.PrependNewInst(insertion_point, op, {Value{args}...})};
|
||||
return T{Value{&*it}};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,189 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/ir/microinstruction.h"
|
||||
#include "shader_recompiler/frontend/ir/type.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
static void CheckPseudoInstruction(IR::Inst* inst, IR::Opcode opcode) {
|
||||
if (inst && inst->Opcode() != opcode) {
|
||||
throw LogicError("Invalid pseudo-instruction");
|
||||
}
|
||||
}
|
||||
|
||||
static void SetPseudoInstruction(IR::Inst*& dest_inst, IR::Inst* pseudo_inst) {
|
||||
if (dest_inst) {
|
||||
throw LogicError("Only one of each type of pseudo-op allowed");
|
||||
}
|
||||
dest_inst = pseudo_inst;
|
||||
}
|
||||
|
||||
static void RemovePseudoInstruction(IR::Inst*& inst, IR::Opcode expected_opcode) {
|
||||
if (inst->Opcode() != expected_opcode) {
|
||||
throw LogicError("Undoing use of invalid pseudo-op");
|
||||
}
|
||||
inst = nullptr;
|
||||
}
|
||||
|
||||
bool Inst::MayHaveSideEffects() const noexcept {
|
||||
switch (op) {
|
||||
case Opcode::SetAttribute:
|
||||
case Opcode::SetAttributeIndexed:
|
||||
case Opcode::WriteGlobalU8:
|
||||
case Opcode::WriteGlobalS8:
|
||||
case Opcode::WriteGlobalU16:
|
||||
case Opcode::WriteGlobalS16:
|
||||
case Opcode::WriteGlobal32:
|
||||
case Opcode::WriteGlobal64:
|
||||
case Opcode::WriteGlobal128:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Inst::IsPseudoInstruction() const noexcept {
|
||||
switch (op) {
|
||||
case Opcode::GetZeroFromOp:
|
||||
case Opcode::GetSignFromOp:
|
||||
case Opcode::GetCarryFromOp:
|
||||
case Opcode::GetOverflowFromOp:
|
||||
case Opcode::GetZSCOFromOp:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Inst::HasAssociatedPseudoOperation() const noexcept {
|
||||
return zero_inst || sign_inst || carry_inst || overflow_inst || zsco_inst;
|
||||
}
|
||||
|
||||
Inst* Inst::GetAssociatedPseudoOperation(IR::Opcode opcode) {
|
||||
// This is faster than doing a search through the block.
|
||||
switch (opcode) {
|
||||
case Opcode::GetZeroFromOp:
|
||||
CheckPseudoInstruction(zero_inst, Opcode::GetZeroFromOp);
|
||||
return zero_inst;
|
||||
case Opcode::GetSignFromOp:
|
||||
CheckPseudoInstruction(sign_inst, Opcode::GetSignFromOp);
|
||||
return sign_inst;
|
||||
case Opcode::GetCarryFromOp:
|
||||
CheckPseudoInstruction(carry_inst, Opcode::GetCarryFromOp);
|
||||
return carry_inst;
|
||||
case Opcode::GetOverflowFromOp:
|
||||
CheckPseudoInstruction(overflow_inst, Opcode::GetOverflowFromOp);
|
||||
return overflow_inst;
|
||||
case Opcode::GetZSCOFromOp:
|
||||
CheckPseudoInstruction(zsco_inst, Opcode::GetZSCOFromOp);
|
||||
return zsco_inst;
|
||||
default:
|
||||
throw InvalidArgument("{} is not a pseudo-instruction", opcode);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Inst::NumArgs() const {
|
||||
return NumArgsOf(op);
|
||||
}
|
||||
|
||||
IR::Type Inst::Type() const {
|
||||
return TypeOf(op);
|
||||
}
|
||||
|
||||
Value Inst::Arg(size_t index) const {
|
||||
if (index >= NumArgsOf(op)) {
|
||||
throw InvalidArgument("Out of bounds argument index {} in opcode {}", index, op);
|
||||
}
|
||||
return args[index];
|
||||
}
|
||||
|
||||
void Inst::SetArg(size_t index, Value value) {
|
||||
if (index >= NumArgsOf(op)) {
|
||||
throw InvalidArgument("Out of bounds argument index {} in opcode {}", index, op);
|
||||
}
|
||||
if (!args[index].IsImmediate()) {
|
||||
UndoUse(args[index]);
|
||||
}
|
||||
if (!value.IsImmediate()) {
|
||||
Use(value);
|
||||
}
|
||||
args[index] = value;
|
||||
}
|
||||
|
||||
void Inst::Invalidate() {
|
||||
ClearArgs();
|
||||
op = Opcode::Void;
|
||||
}
|
||||
|
||||
void Inst::ClearArgs() {
|
||||
for (auto& value : args) {
|
||||
if (!value.IsImmediate()) {
|
||||
UndoUse(value);
|
||||
}
|
||||
value = {};
|
||||
}
|
||||
}
|
||||
|
||||
void Inst::ReplaceUsesWith(Value replacement) {
|
||||
Invalidate();
|
||||
|
||||
op = Opcode::Identity;
|
||||
|
||||
if (!replacement.IsImmediate()) {
|
||||
Use(replacement);
|
||||
}
|
||||
args[0] = replacement;
|
||||
}
|
||||
|
||||
void Inst::Use(const Value& value) {
|
||||
++value.Inst()->use_count;
|
||||
|
||||
switch (op) {
|
||||
case Opcode::GetZeroFromOp:
|
||||
SetPseudoInstruction(value.Inst()->zero_inst, this);
|
||||
break;
|
||||
case Opcode::GetSignFromOp:
|
||||
SetPseudoInstruction(value.Inst()->sign_inst, this);
|
||||
break;
|
||||
case Opcode::GetCarryFromOp:
|
||||
SetPseudoInstruction(value.Inst()->carry_inst, this);
|
||||
break;
|
||||
case Opcode::GetOverflowFromOp:
|
||||
SetPseudoInstruction(value.Inst()->overflow_inst, this);
|
||||
break;
|
||||
case Opcode::GetZSCOFromOp:
|
||||
SetPseudoInstruction(value.Inst()->zsco_inst, this);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Inst::UndoUse(const Value& value) {
|
||||
--value.Inst()->use_count;
|
||||
|
||||
switch (op) {
|
||||
case Opcode::GetZeroFromOp:
|
||||
RemovePseudoInstruction(value.Inst()->zero_inst, Opcode::GetZeroFromOp);
|
||||
break;
|
||||
case Opcode::GetSignFromOp:
|
||||
RemovePseudoInstruction(value.Inst()->sign_inst, Opcode::GetSignFromOp);
|
||||
break;
|
||||
case Opcode::GetCarryFromOp:
|
||||
RemovePseudoInstruction(value.Inst()->carry_inst, Opcode::GetCarryFromOp);
|
||||
break;
|
||||
case Opcode::GetOverflowFromOp:
|
||||
RemovePseudoInstruction(value.Inst()->overflow_inst, Opcode::GetOverflowFromOp);
|
||||
break;
|
||||
case Opcode::GetZSCOFromOp:
|
||||
RemovePseudoInstruction(value.Inst()->zsco_inst, Opcode::GetZSCOFromOp);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/frontend/ir/opcode.h"
|
||||
#include "shader_recompiler/frontend/ir/type.h"
|
||||
#include "shader_recompiler/frontend/ir/value.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
constexpr size_t MAX_ARG_COUNT = 4;
|
||||
|
||||
class Inst : public boost::intrusive::list_base_hook<> {
|
||||
public:
|
||||
explicit Inst(Opcode op_) noexcept : op(op_) {}
|
||||
|
||||
/// Get the number of uses this instruction has.
|
||||
[[nodiscard]] int UseCount() const noexcept {
|
||||
return use_count;
|
||||
}
|
||||
|
||||
/// Determines whether this instruction has uses or not.
|
||||
[[nodiscard]] bool HasUses() const noexcept {
|
||||
return use_count > 0;
|
||||
}
|
||||
|
||||
/// Get the opcode this microinstruction represents.
|
||||
[[nodiscard]] IR::Opcode Opcode() const noexcept {
|
||||
return op;
|
||||
}
|
||||
|
||||
/// Determines whether or not this instruction may have side effects.
|
||||
[[nodiscard]] bool MayHaveSideEffects() const noexcept;
|
||||
|
||||
/// Determines whether or not this instruction is a pseudo-instruction.
|
||||
/// Pseudo-instructions depend on their parent instructions for their semantics.
|
||||
[[nodiscard]] bool IsPseudoInstruction() const noexcept;
|
||||
|
||||
/// Determines if there is a pseudo-operation associated with this instruction.
|
||||
[[nodiscard]] bool HasAssociatedPseudoOperation() const noexcept;
|
||||
/// Gets a pseudo-operation associated with this instruction
|
||||
[[nodiscard]] Inst* GetAssociatedPseudoOperation(IR::Opcode opcode);
|
||||
|
||||
/// Get the number of arguments this instruction has.
|
||||
[[nodiscard]] size_t NumArgs() const;
|
||||
|
||||
/// Get the type this instruction returns.
|
||||
[[nodiscard]] IR::Type Type() const;
|
||||
|
||||
/// Get the value of a given argument index.
|
||||
[[nodiscard]] Value Arg(size_t index) const;
|
||||
/// Set the value of a given argument index.
|
||||
void SetArg(size_t index, Value value);
|
||||
|
||||
void Invalidate();
|
||||
void ClearArgs();
|
||||
|
||||
void ReplaceUsesWith(Value replacement);
|
||||
|
||||
private:
|
||||
void Use(const Value& value);
|
||||
void UndoUse(const Value& value);
|
||||
|
||||
IR::Opcode op{};
|
||||
int use_count{};
|
||||
std::array<Value, MAX_ARG_COUNT> args{};
|
||||
Inst* zero_inst{};
|
||||
Inst* sign_inst{};
|
||||
Inst* carry_inst{};
|
||||
Inst* overflow_inst{};
|
||||
Inst* zsco_inst{};
|
||||
u64 flags{};
|
||||
};
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/ir/opcode.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
namespace {
|
||||
struct OpcodeMeta {
|
||||
std::string_view name;
|
||||
Type type;
|
||||
std::array<Type, 4> arg_types;
|
||||
};
|
||||
|
||||
using enum Type;
|
||||
|
||||
constexpr std::array META_TABLE{
|
||||
#define OPCODE(name_token, type_token, ...) \
|
||||
OpcodeMeta{ \
|
||||
.name{#name_token}, \
|
||||
.type{type_token}, \
|
||||
.arg_types{__VA_ARGS__}, \
|
||||
},
|
||||
#include "opcode.inc"
|
||||
#undef OPCODE
|
||||
};
|
||||
|
||||
void ValidateOpcode(Opcode op) {
|
||||
const size_t raw{static_cast<size_t>(op)};
|
||||
if (raw >= META_TABLE.size()) {
|
||||
throw InvalidArgument("Invalid opcode with raw value {}", raw);
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
Type TypeOf(Opcode op) {
|
||||
ValidateOpcode(op);
|
||||
return META_TABLE[static_cast<size_t>(op)].type;
|
||||
}
|
||||
|
||||
size_t NumArgsOf(Opcode op) {
|
||||
ValidateOpcode(op);
|
||||
const auto& arg_types{META_TABLE[static_cast<size_t>(op)].arg_types};
|
||||
const auto distance{std::distance(arg_types.begin(), std::ranges::find(arg_types, Type::Void))};
|
||||
return static_cast<size_t>(distance);
|
||||
}
|
||||
|
||||
Type ArgTypeOf(Opcode op, size_t arg_index) {
|
||||
ValidateOpcode(op);
|
||||
const auto& arg_types{META_TABLE[static_cast<size_t>(op)].arg_types};
|
||||
if (arg_index >= arg_types.size() || arg_types[arg_index] == Type::Void) {
|
||||
throw InvalidArgument("Out of bounds argument");
|
||||
}
|
||||
return arg_types[arg_index];
|
||||
}
|
||||
|
||||
std::string_view NameOf(Opcode op) {
|
||||
ValidateOpcode(op);
|
||||
return META_TABLE[static_cast<size_t>(op)].name;
|
||||
}
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/type.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
enum class Opcode {
|
||||
#define OPCODE(name, ...) name,
|
||||
#include "opcode.inc"
|
||||
#undef OPCODE
|
||||
};
|
||||
|
||||
/// Get return type of an opcode
|
||||
[[nodiscard]] Type TypeOf(Opcode op);
|
||||
|
||||
/// Get the number of arguments an opcode accepts
|
||||
[[nodiscard]] size_t NumArgsOf(Opcode op);
|
||||
|
||||
/// Get the required type of an argument of an opcode
|
||||
[[nodiscard]] Type ArgTypeOf(Opcode op, size_t arg_index);
|
||||
|
||||
/// Get the name of an opcode
|
||||
[[nodiscard]] std::string_view NameOf(Opcode op);
|
||||
|
||||
} // namespace Shader::IR
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Shader::IR::Opcode> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const Shader::IR::Opcode& op, FormatContext& ctx) {
|
||||
return format_to(ctx.out(), "{}", Shader::IR::NameOf(op));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// opcode name, return type, arg1 type, arg2 type, arg3 type, arg4 type, ...
|
||||
OPCODE(Void, Void, )
|
||||
OPCODE(Identity, Opaque, Opaque, )
|
||||
|
||||
// Control flow
|
||||
OPCODE(Branch, Void, Label, )
|
||||
OPCODE(BranchConditional, Void, U1, Label, Label, )
|
||||
OPCODE(Exit, Void, )
|
||||
OPCODE(Return, Void, )
|
||||
OPCODE(Unreachable, Void, )
|
||||
|
||||
// Context getters/setters
|
||||
OPCODE(GetRegister, U32, Reg, )
|
||||
OPCODE(SetRegister, Void, Reg, U32, )
|
||||
OPCODE(GetPred, U1, Pred, )
|
||||
OPCODE(SetPred, Void, Pred, U1, )
|
||||
OPCODE(GetCbuf, U32, U32, U32, )
|
||||
OPCODE(GetAttribute, U32, Attribute, )
|
||||
OPCODE(SetAttribute, U32, Attribute, )
|
||||
OPCODE(GetAttributeIndexed, U32, U32, )
|
||||
OPCODE(SetAttributeIndexed, U32, U32, )
|
||||
OPCODE(GetZSCORaw, U32, )
|
||||
OPCODE(SetZSCORaw, Void, U32, )
|
||||
OPCODE(SetZSCO, Void, ZSCO, )
|
||||
OPCODE(GetZFlag, U1, Void, )
|
||||
OPCODE(GetSFlag, U1, Void, )
|
||||
OPCODE(GetCFlag, U1, Void, )
|
||||
OPCODE(GetOFlag, U1, Void, )
|
||||
OPCODE(SetZFlag, Void, U1, )
|
||||
OPCODE(SetSFlag, Void, U1, )
|
||||
OPCODE(SetCFlag, Void, U1, )
|
||||
OPCODE(SetOFlag, Void, U1, )
|
||||
|
||||
// Memory operations
|
||||
OPCODE(WriteGlobalU8, Void, U64, U32, )
|
||||
OPCODE(WriteGlobalS8, Void, U64, U32, )
|
||||
OPCODE(WriteGlobalU16, Void, U64, U32, )
|
||||
OPCODE(WriteGlobalS16, Void, U64, U32, )
|
||||
OPCODE(WriteGlobal32, Void, U64, U32, )
|
||||
OPCODE(WriteGlobal64, Void, U64, Opaque, )
|
||||
OPCODE(WriteGlobal128, Void, U64, Opaque, )
|
||||
|
||||
// Vector utility
|
||||
OPCODE(CompositeConstruct2, Opaque, Opaque, Opaque, )
|
||||
OPCODE(CompositeConstruct3, Opaque, Opaque, Opaque, Opaque, )
|
||||
OPCODE(CompositeConstruct4, Opaque, Opaque, Opaque, Opaque, Opaque, )
|
||||
OPCODE(CompositeExtract, Opaque, Opaque, U32, )
|
||||
|
||||
// Bitwise conversions
|
||||
OPCODE(PackUint2x32, U64, Opaque, )
|
||||
OPCODE(UnpackUint2x32, Opaque, U64, )
|
||||
OPCODE(PackFloat2x16, U32, Opaque, )
|
||||
OPCODE(UnpackFloat2x16, Opaque, U32, )
|
||||
OPCODE(PackDouble2x32, U64, Opaque, )
|
||||
OPCODE(UnpackDouble2x32, Opaque, U64, )
|
||||
|
||||
// Pseudo-operation, handled specially at final emit
|
||||
OPCODE(GetZeroFromOp, U1, Opaque, )
|
||||
OPCODE(GetSignFromOp, U1, Opaque, )
|
||||
OPCODE(GetCarryFromOp, U1, Opaque, )
|
||||
OPCODE(GetOverflowFromOp, U1, Opaque, )
|
||||
OPCODE(GetZSCOFromOp, ZSCO, Opaque, )
|
||||
|
||||
// Floating-point operations
|
||||
OPCODE(FPAbs16, U16, U16 )
|
||||
OPCODE(FPAbs32, U32, U32 )
|
||||
OPCODE(FPAbs64, U64, U64 )
|
||||
OPCODE(FPAdd16, U16, U16, U16 )
|
||||
OPCODE(FPAdd32, U32, U32, U32 )
|
||||
OPCODE(FPAdd64, U64, U64, U64 )
|
||||
OPCODE(FPFma16, U16, U16, U16 )
|
||||
OPCODE(FPFma32, U32, U32, U32 )
|
||||
OPCODE(FPFma64, U64, U64, U64 )
|
||||
OPCODE(FPMax32, U32, U32, U32 )
|
||||
OPCODE(FPMax64, U64, U64, U64 )
|
||||
OPCODE(FPMin32, U32, U32, U32 )
|
||||
OPCODE(FPMin64, U64, U64, U64 )
|
||||
OPCODE(FPMul16, U16, U16, U16 )
|
||||
OPCODE(FPMul32, U32, U32, U32 )
|
||||
OPCODE(FPMul64, U64, U64, U64 )
|
||||
OPCODE(FPNeg16, U16, U16 )
|
||||
OPCODE(FPNeg32, U32, U32 )
|
||||
OPCODE(FPNeg64, U64, U64 )
|
||||
OPCODE(FPRecip32, U32, U32 )
|
||||
OPCODE(FPRecip64, U64, U64 )
|
||||
OPCODE(FPRecipSqrt32, U32, U32 )
|
||||
OPCODE(FPRecipSqrt64, U64, U64 )
|
||||
OPCODE(FPSqrt, U32, U32 )
|
||||
OPCODE(FPSin, U32, U32 )
|
||||
OPCODE(FPSinNotReduced, U32, U32 )
|
||||
OPCODE(FPExp2, U32, U32 )
|
||||
OPCODE(FPExp2NotReduced, U32, U32 )
|
||||
OPCODE(FPCos, U32, U32 )
|
||||
OPCODE(FPCosNotReduced, U32, U32 )
|
||||
OPCODE(FPLog2, U32, U32 )
|
||||
OPCODE(FPSaturate16, U16, U16 )
|
||||
OPCODE(FPSaturate32, U32, U32 )
|
||||
OPCODE(FPSaturate64, U64, U64 )
|
||||
OPCODE(FPRoundEven16, U16, U16 )
|
||||
OPCODE(FPRoundEven32, U32, U32 )
|
||||
OPCODE(FPRoundEven64, U64, U64 )
|
||||
OPCODE(FPFloor16, U16, U16 )
|
||||
OPCODE(FPFloor32, U32, U32 )
|
||||
OPCODE(FPFloor64, U64, U64 )
|
||||
OPCODE(FPCeil16, U16, U16 )
|
||||
OPCODE(FPCeil32, U32, U32 )
|
||||
OPCODE(FPCeil64, U64, U64 )
|
||||
OPCODE(FPTrunc16, U16, U16 )
|
||||
OPCODE(FPTrunc32, U32, U32 )
|
||||
OPCODE(FPTrunc64, U64, U64 )
|
||||
|
||||
// Logical operations
|
||||
OPCODE(LogicalOr, U1, U1, U1, )
|
||||
OPCODE(LogicalAnd, U1, U1, U1, )
|
||||
OPCODE(LogicalNot, U1, U1, )
|
||||
|
||||
// Conversion operations
|
||||
OPCODE(ConvertS16F16, U32, U16, )
|
||||
OPCODE(ConvertS16F32, U32, U32, )
|
||||
OPCODE(ConvertS16F64, U32, U64, )
|
||||
OPCODE(ConvertS32F16, U32, U16, )
|
||||
OPCODE(ConvertS32F32, U32, U32, )
|
||||
OPCODE(ConvertS32F64, U32, U64, )
|
||||
OPCODE(ConvertS64F16, U64, U16, )
|
||||
OPCODE(ConvertS64F32, U64, U32, )
|
||||
OPCODE(ConvertS64F64, U64, U64, )
|
||||
OPCODE(ConvertU16F16, U32, U16, )
|
||||
OPCODE(ConvertU16F32, U32, U32, )
|
||||
OPCODE(ConvertU16F64, U32, U64, )
|
||||
OPCODE(ConvertU32F16, U32, U16, )
|
||||
OPCODE(ConvertU32F32, U32, U32, )
|
||||
OPCODE(ConvertU32F64, U32, U64, )
|
||||
OPCODE(ConvertU64F16, U64, U16, )
|
||||
OPCODE(ConvertU64F32, U64, U32, )
|
||||
OPCODE(ConvertU64F64, U64, U64, )
|
||||
|
||||
OPCODE(ConvertU64U32, U64, U32, )
|
||||
OPCODE(ConvertU32U64, U32, U64, )
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
enum class Pred { P0, P1, P2, P3, P4, P5, P6, PT };
|
||||
|
||||
} // namespace Shader::IR
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Shader::IR::Pred> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const Shader::IR::Pred& pred, FormatContext& ctx) {
|
||||
if (pred == Shader::IR::Pred::PT) {
|
||||
return fmt::format_to(ctx.out(), "PT");
|
||||
} else {
|
||||
return fmt::format_to(ctx.out(), "P{}", static_cast<int>(pred));
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,314 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
enum class Reg : u64 {
|
||||
R0,
|
||||
R1,
|
||||
R2,
|
||||
R3,
|
||||
R4,
|
||||
R5,
|
||||
R6,
|
||||
R7,
|
||||
R8,
|
||||
R9,
|
||||
R10,
|
||||
R11,
|
||||
R12,
|
||||
R13,
|
||||
R14,
|
||||
R15,
|
||||
R16,
|
||||
R17,
|
||||
R18,
|
||||
R19,
|
||||
R20,
|
||||
R21,
|
||||
R22,
|
||||
R23,
|
||||
R24,
|
||||
R25,
|
||||
R26,
|
||||
R27,
|
||||
R28,
|
||||
R29,
|
||||
R30,
|
||||
R31,
|
||||
R32,
|
||||
R33,
|
||||
R34,
|
||||
R35,
|
||||
R36,
|
||||
R37,
|
||||
R38,
|
||||
R39,
|
||||
R40,
|
||||
R41,
|
||||
R42,
|
||||
R43,
|
||||
R44,
|
||||
R45,
|
||||
R46,
|
||||
R47,
|
||||
R48,
|
||||
R49,
|
||||
R50,
|
||||
R51,
|
||||
R52,
|
||||
R53,
|
||||
R54,
|
||||
R55,
|
||||
R56,
|
||||
R57,
|
||||
R58,
|
||||
R59,
|
||||
R60,
|
||||
R61,
|
||||
R62,
|
||||
R63,
|
||||
R64,
|
||||
R65,
|
||||
R66,
|
||||
R67,
|
||||
R68,
|
||||
R69,
|
||||
R70,
|
||||
R71,
|
||||
R72,
|
||||
R73,
|
||||
R74,
|
||||
R75,
|
||||
R76,
|
||||
R77,
|
||||
R78,
|
||||
R79,
|
||||
R80,
|
||||
R81,
|
||||
R82,
|
||||
R83,
|
||||
R84,
|
||||
R85,
|
||||
R86,
|
||||
R87,
|
||||
R88,
|
||||
R89,
|
||||
R90,
|
||||
R91,
|
||||
R92,
|
||||
R93,
|
||||
R94,
|
||||
R95,
|
||||
R96,
|
||||
R97,
|
||||
R98,
|
||||
R99,
|
||||
R100,
|
||||
R101,
|
||||
R102,
|
||||
R103,
|
||||
R104,
|
||||
R105,
|
||||
R106,
|
||||
R107,
|
||||
R108,
|
||||
R109,
|
||||
R110,
|
||||
R111,
|
||||
R112,
|
||||
R113,
|
||||
R114,
|
||||
R115,
|
||||
R116,
|
||||
R117,
|
||||
R118,
|
||||
R119,
|
||||
R120,
|
||||
R121,
|
||||
R122,
|
||||
R123,
|
||||
R124,
|
||||
R125,
|
||||
R126,
|
||||
R127,
|
||||
R128,
|
||||
R129,
|
||||
R130,
|
||||
R131,
|
||||
R132,
|
||||
R133,
|
||||
R134,
|
||||
R135,
|
||||
R136,
|
||||
R137,
|
||||
R138,
|
||||
R139,
|
||||
R140,
|
||||
R141,
|
||||
R142,
|
||||
R143,
|
||||
R144,
|
||||
R145,
|
||||
R146,
|
||||
R147,
|
||||
R148,
|
||||
R149,
|
||||
R150,
|
||||
R151,
|
||||
R152,
|
||||
R153,
|
||||
R154,
|
||||
R155,
|
||||
R156,
|
||||
R157,
|
||||
R158,
|
||||
R159,
|
||||
R160,
|
||||
R161,
|
||||
R162,
|
||||
R163,
|
||||
R164,
|
||||
R165,
|
||||
R166,
|
||||
R167,
|
||||
R168,
|
||||
R169,
|
||||
R170,
|
||||
R171,
|
||||
R172,
|
||||
R173,
|
||||
R174,
|
||||
R175,
|
||||
R176,
|
||||
R177,
|
||||
R178,
|
||||
R179,
|
||||
R180,
|
||||
R181,
|
||||
R182,
|
||||
R183,
|
||||
R184,
|
||||
R185,
|
||||
R186,
|
||||
R187,
|
||||
R188,
|
||||
R189,
|
||||
R190,
|
||||
R191,
|
||||
R192,
|
||||
R193,
|
||||
R194,
|
||||
R195,
|
||||
R196,
|
||||
R197,
|
||||
R198,
|
||||
R199,
|
||||
R200,
|
||||
R201,
|
||||
R202,
|
||||
R203,
|
||||
R204,
|
||||
R205,
|
||||
R206,
|
||||
R207,
|
||||
R208,
|
||||
R209,
|
||||
R210,
|
||||
R211,
|
||||
R212,
|
||||
R213,
|
||||
R214,
|
||||
R215,
|
||||
R216,
|
||||
R217,
|
||||
R218,
|
||||
R219,
|
||||
R220,
|
||||
R221,
|
||||
R222,
|
||||
R223,
|
||||
R224,
|
||||
R225,
|
||||
R226,
|
||||
R227,
|
||||
R228,
|
||||
R229,
|
||||
R230,
|
||||
R231,
|
||||
R232,
|
||||
R233,
|
||||
R234,
|
||||
R235,
|
||||
R236,
|
||||
R237,
|
||||
R238,
|
||||
R239,
|
||||
R240,
|
||||
R241,
|
||||
R242,
|
||||
R243,
|
||||
R244,
|
||||
R245,
|
||||
R246,
|
||||
R247,
|
||||
R248,
|
||||
R249,
|
||||
R250,
|
||||
R251,
|
||||
R252,
|
||||
R253,
|
||||
R254,
|
||||
RZ,
|
||||
};
|
||||
static_assert(static_cast<int>(Reg::RZ) == 255);
|
||||
|
||||
[[nodiscard]] constexpr Reg operator+(Reg reg, int num) {
|
||||
if (reg == Reg::RZ) {
|
||||
// Adding or subtracting registers from RZ yields RZ
|
||||
return Reg::RZ;
|
||||
}
|
||||
const int result{static_cast<int>(reg) + num};
|
||||
if (result >= static_cast<int>(Reg::RZ)) {
|
||||
throw LogicError("Overflow on register arithmetic");
|
||||
}
|
||||
if (result < 0) {
|
||||
throw LogicError("Underflow on register arithmetic");
|
||||
}
|
||||
return static_cast<Reg>(result);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Reg operator-(Reg reg, int num) {
|
||||
return reg + (-num);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool IsAligned(Reg reg, size_t align) {
|
||||
return (static_cast<size_t>(reg) / align) * align == static_cast<size_t>(reg);
|
||||
}
|
||||
|
||||
} // namespace Shader::IR
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Shader::IR::Reg> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const Shader::IR::Reg& reg, FormatContext& ctx) {
|
||||
if (reg == Shader::IR::Reg::RZ) {
|
||||
return fmt::format_to(ctx.out(), "RZ");
|
||||
} else if (static_cast<int>(reg) >= 0 && static_cast<int>(reg) < 255) {
|
||||
return fmt::format_to(ctx.out(), "R{}", static_cast<int>(reg));
|
||||
} else {
|
||||
throw Shader::LogicError("Invalid register with raw value {}", static_cast<int>(reg));
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/type.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
std::string NameOf(Type type) {
|
||||
static constexpr std::array names{
|
||||
"Opaque", "Label", "Reg", "Pred", "Attribute", "U1", "U8", "U16", "U32", "U64", "ZSCO",
|
||||
};
|
||||
const size_t bits{static_cast<size_t>(type)};
|
||||
if (bits == 0) {
|
||||
return "Void";
|
||||
}
|
||||
std::string result;
|
||||
for (size_t i = 0; i < names.size(); i++) {
|
||||
if ((bits & (size_t{1} << i)) != 0) {
|
||||
if (!result.empty()) {
|
||||
result += '|';
|
||||
}
|
||||
result += names[i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AreTypesCompatible(Type lhs, Type rhs) noexcept {
|
||||
return lhs == rhs || lhs == Type::Opaque || rhs == Type::Opaque;
|
||||
}
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
enum class Type {
|
||||
Void = 0,
|
||||
Opaque = 1 << 0,
|
||||
Label = 1 << 1,
|
||||
Reg = 1 << 2,
|
||||
Pred = 1 << 3,
|
||||
Attribute = 1 << 4,
|
||||
U1 = 1 << 5,
|
||||
U8 = 1 << 6,
|
||||
U16 = 1 << 7,
|
||||
U32 = 1 << 8,
|
||||
U64 = 1 << 9,
|
||||
ZSCO = 1 << 10,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(Type)
|
||||
|
||||
[[nodiscard]] std::string NameOf(Type type);
|
||||
|
||||
[[nodiscard]] bool AreTypesCompatible(Type lhs, Type rhs) noexcept;
|
||||
|
||||
} // namespace Shader::IR
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Shader::IR::Type> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const Shader::IR::Type& type, FormatContext& ctx) {
|
||||
return fmt::format_to(ctx.out(), "{}", NameOf(type));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,124 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "shader_recompiler/frontend/ir/microinstruction.h"
|
||||
#include "shader_recompiler/frontend/ir/opcode.h"
|
||||
#include "shader_recompiler/frontend/ir/value.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
Value::Value(IR::Inst* value) noexcept : type{Type::Opaque}, inst{value} {}
|
||||
|
||||
Value::Value(IR::Block* value) noexcept : type{Type::Label}, label{value} {}
|
||||
|
||||
Value::Value(IR::Reg value) noexcept : type{Type::Reg}, reg{value} {}
|
||||
|
||||
Value::Value(IR::Pred value) noexcept : type{Type::Pred}, pred{value} {}
|
||||
|
||||
Value::Value(IR::Attribute value) noexcept : type{Type::Attribute}, attribute{value} {}
|
||||
|
||||
Value::Value(bool value) noexcept : type{Type::U1}, imm_u1{value} {}
|
||||
|
||||
Value::Value(u8 value) noexcept : type{Type::U8}, imm_u8{value} {}
|
||||
|
||||
Value::Value(u16 value) noexcept : type{Type::U16}, imm_u16{value} {}
|
||||
|
||||
Value::Value(u32 value) noexcept : type{Type::U32}, imm_u32{value} {}
|
||||
|
||||
Value::Value(u64 value) noexcept : type{Type::U64}, imm_u64{value} {}
|
||||
|
||||
bool Value::IsIdentity() const noexcept {
|
||||
return type == Type::Opaque && inst->Opcode() == Opcode::Identity;
|
||||
}
|
||||
|
||||
bool Value::IsEmpty() const noexcept {
|
||||
return type == Type::Void;
|
||||
}
|
||||
|
||||
bool Value::IsImmediate() const noexcept {
|
||||
if (IsIdentity()) {
|
||||
return inst->Arg(0).IsImmediate();
|
||||
}
|
||||
return type != Type::Opaque;
|
||||
}
|
||||
|
||||
bool Value::IsLabel() const noexcept {
|
||||
return type == Type::Label;
|
||||
}
|
||||
|
||||
IR::Type Value::Type() const noexcept {
|
||||
if (IsIdentity()) {
|
||||
return inst->Arg(0).Type();
|
||||
}
|
||||
if (type == Type::Opaque) {
|
||||
return inst->Type();
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
IR::Inst* Value::Inst() const {
|
||||
ValidateAccess(Type::Opaque);
|
||||
return inst;
|
||||
}
|
||||
|
||||
IR::Block* Value::Label() const {
|
||||
ValidateAccess(Type::Label);
|
||||
return label;
|
||||
}
|
||||
|
||||
IR::Inst* Value::InstRecursive() const {
|
||||
ValidateAccess(Type::Opaque);
|
||||
if (IsIdentity()) {
|
||||
return inst->Arg(0).InstRecursive();
|
||||
}
|
||||
return inst;
|
||||
}
|
||||
|
||||
IR::Reg Value::Reg() const {
|
||||
ValidateAccess(Type::Reg);
|
||||
return reg;
|
||||
}
|
||||
|
||||
IR::Pred Value::Pred() const {
|
||||
ValidateAccess(Type::Pred);
|
||||
return pred;
|
||||
}
|
||||
|
||||
IR::Attribute Value::Attribute() const {
|
||||
ValidateAccess(Type::Attribute);
|
||||
return attribute;
|
||||
}
|
||||
|
||||
bool Value::U1() const {
|
||||
ValidateAccess(Type::U1);
|
||||
return imm_u1;
|
||||
}
|
||||
|
||||
u8 Value::U8() const {
|
||||
ValidateAccess(Type::U8);
|
||||
return imm_u8;
|
||||
}
|
||||
|
||||
u16 Value::U16() const {
|
||||
ValidateAccess(Type::U16);
|
||||
return imm_u16;
|
||||
}
|
||||
|
||||
u32 Value::U32() const {
|
||||
ValidateAccess(Type::U32);
|
||||
return imm_u32;
|
||||
}
|
||||
|
||||
u64 Value::U64() const {
|
||||
ValidateAccess(Type::U64);
|
||||
return imm_u64;
|
||||
}
|
||||
|
||||
void Value::ValidateAccess(IR::Type expected) const {
|
||||
if (type != expected) {
|
||||
throw LogicError("Reading {} out of {}", expected, type);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,98 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/ir/attribute.h"
|
||||
#include "shader_recompiler/frontend/ir/pred.h"
|
||||
#include "shader_recompiler/frontend/ir/reg.h"
|
||||
#include "shader_recompiler/frontend/ir/type.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
class Block;
|
||||
class Inst;
|
||||
|
||||
class Value {
|
||||
public:
|
||||
Value() noexcept : type{IR::Type::Void}, inst{nullptr} {}
|
||||
explicit Value(IR::Inst* value) noexcept;
|
||||
explicit Value(IR::Block* value) noexcept;
|
||||
explicit Value(IR::Reg value) noexcept;
|
||||
explicit Value(IR::Pred value) noexcept;
|
||||
explicit Value(IR::Attribute value) noexcept;
|
||||
explicit Value(bool value) noexcept;
|
||||
explicit Value(u8 value) noexcept;
|
||||
explicit Value(u16 value) noexcept;
|
||||
explicit Value(u32 value) noexcept;
|
||||
explicit Value(u64 value) noexcept;
|
||||
|
||||
[[nodiscard]] bool IsIdentity() const noexcept;
|
||||
[[nodiscard]] bool IsEmpty() const noexcept;
|
||||
[[nodiscard]] bool IsImmediate() const noexcept;
|
||||
[[nodiscard]] bool IsLabel() const noexcept;
|
||||
[[nodiscard]] IR::Type Type() const noexcept;
|
||||
|
||||
[[nodiscard]] IR::Inst* Inst() const;
|
||||
[[nodiscard]] IR::Block* Label() const;
|
||||
[[nodiscard]] IR::Inst* InstRecursive() const;
|
||||
[[nodiscard]] IR::Reg Reg() const;
|
||||
[[nodiscard]] IR::Pred Pred() const;
|
||||
[[nodiscard]] IR::Attribute Attribute() const;
|
||||
[[nodiscard]] bool U1() const;
|
||||
[[nodiscard]] u8 U8() const;
|
||||
[[nodiscard]] u16 U16() const;
|
||||
[[nodiscard]] u32 U32() const;
|
||||
[[nodiscard]] u64 U64() const;
|
||||
|
||||
private:
|
||||
void ValidateAccess(IR::Type expected) const;
|
||||
|
||||
IR::Type type;
|
||||
union {
|
||||
IR::Inst* inst;
|
||||
IR::Block* label;
|
||||
IR::Reg reg;
|
||||
IR::Pred pred;
|
||||
IR::Attribute attribute;
|
||||
bool imm_u1;
|
||||
u8 imm_u8;
|
||||
u16 imm_u16;
|
||||
u32 imm_u32;
|
||||
u64 imm_u64;
|
||||
};
|
||||
};
|
||||
|
||||
template <IR::Type type_>
|
||||
class TypedValue : public Value {
|
||||
public:
|
||||
TypedValue() = default;
|
||||
|
||||
template <IR::Type other_type>
|
||||
requires((other_type & type_) != IR::Type::Void) explicit(false)
|
||||
TypedValue(const TypedValue<other_type>& value)
|
||||
: Value(value) {}
|
||||
|
||||
explicit TypedValue(const Value& value) : Value(value) {
|
||||
if ((value.Type() & type_) == IR::Type::Void) {
|
||||
throw InvalidArgument("Incompatible types {} and {}", type_, value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
explicit TypedValue(IR::Inst* inst) : TypedValue(Value(inst)) {}
|
||||
};
|
||||
|
||||
using U1 = TypedValue<Type::U1>;
|
||||
using U8 = TypedValue<Type::U8>;
|
||||
using U16 = TypedValue<Type::U16>;
|
||||
using U32 = TypedValue<Type::U32>;
|
||||
using U64 = TypedValue<Type::U64>;
|
||||
using U32U64 = TypedValue<Type::U32 | Type::U64>;
|
||||
using U16U32U64 = TypedValue<Type::U16 | Type::U32 | Type::U64>;
|
||||
using UAny = TypedValue<Type::U8 | Type::U16 | Type::U32 | Type::U64>;
|
||||
using ZSCO = TypedValue<Type::ZSCO>;
|
||||
|
||||
} // namespace Shader::IR
|
|
@ -0,0 +1,531 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
#include "shader_recompiler/frontend/maxwell/decode.h"
|
||||
#include "shader_recompiler/frontend/maxwell/location.h"
|
||||
|
||||
namespace Shader::Maxwell::Flow {
|
||||
|
||||
static u32 BranchOffset(Location pc, Instruction inst) {
|
||||
return pc.Offset() + inst.branch.Offset() + 8;
|
||||
}
|
||||
|
||||
static std::array<Block, 2> Split(Block&& block, Location pc, BlockId new_id) {
|
||||
if (pc <= block.begin || pc >= block.end) {
|
||||
throw InvalidArgument("Invalid address to split={}", pc);
|
||||
}
|
||||
return {
|
||||
Block{
|
||||
.begin{block.begin},
|
||||
.end{pc},
|
||||
.end_class{EndClass::Branch},
|
||||
.id{block.id},
|
||||
.stack{block.stack},
|
||||
.cond{true},
|
||||
.branch_true{new_id},
|
||||
.branch_false{UNREACHABLE_BLOCK_ID},
|
||||
},
|
||||
Block{
|
||||
.begin{pc},
|
||||
.end{block.end},
|
||||
.end_class{block.end_class},
|
||||
.id{new_id},
|
||||
.stack{std::move(block.stack)},
|
||||
.cond{block.cond},
|
||||
.branch_true{block.branch_true},
|
||||
.branch_false{block.branch_false},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static Token OpcodeToken(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::PBK:
|
||||
case Opcode::BRK:
|
||||
return Token::PBK;
|
||||
case Opcode::PCNT:
|
||||
case Opcode::CONT:
|
||||
return Token::PBK;
|
||||
case Opcode::PEXIT:
|
||||
case Opcode::EXIT:
|
||||
return Token::PEXIT;
|
||||
case Opcode::PLONGJMP:
|
||||
case Opcode::LONGJMP:
|
||||
return Token::PLONGJMP;
|
||||
case Opcode::PRET:
|
||||
case Opcode::RET:
|
||||
case Opcode::CAL:
|
||||
return Token::PRET;
|
||||
case Opcode::SSY:
|
||||
case Opcode::SYNC:
|
||||
return Token::SSY;
|
||||
default:
|
||||
throw InvalidArgument("{}", opcode);
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsAbsoluteJump(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::JCAL:
|
||||
case Opcode::JMP:
|
||||
case Opcode::JMX:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool HasFlowTest(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::BRA:
|
||||
case Opcode::BRX:
|
||||
case Opcode::EXIT:
|
||||
case Opcode::JMP:
|
||||
case Opcode::JMX:
|
||||
case Opcode::BRK:
|
||||
case Opcode::CONT:
|
||||
case Opcode::LONGJMP:
|
||||
case Opcode::RET:
|
||||
case Opcode::SYNC:
|
||||
return true;
|
||||
case Opcode::CAL:
|
||||
case Opcode::JCAL:
|
||||
return false;
|
||||
default:
|
||||
throw InvalidArgument("Invalid branch {}", opcode);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string Name(const Block& block) {
|
||||
if (block.begin.IsVirtual()) {
|
||||
return fmt::format("\"Virtual {}\"", block.id);
|
||||
} else {
|
||||
return fmt::format("\"{}\"", block.begin);
|
||||
}
|
||||
}
|
||||
|
||||
void Stack::Push(Token token, Location target) {
|
||||
entries.push_back({
|
||||
.token{token},
|
||||
.target{target},
|
||||
});
|
||||
}
|
||||
|
||||
std::pair<Location, Stack> Stack::Pop(Token token) const {
|
||||
const std::optional<Location> pc{Peek(token)};
|
||||
if (!pc) {
|
||||
throw LogicError("Token could not be found");
|
||||
}
|
||||
return {*pc, Remove(token)};
|
||||
}
|
||||
|
||||
std::optional<Location> Stack::Peek(Token token) const {
|
||||
const auto reverse_entries{entries | std::views::reverse};
|
||||
const auto it{std::ranges::find(reverse_entries, token, &StackEntry::token)};
|
||||
if (it == reverse_entries.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return it->target;
|
||||
}
|
||||
|
||||
Stack Stack::Remove(Token token) const {
|
||||
const auto reverse_entries{entries | std::views::reverse};
|
||||
const auto it{std::ranges::find(reverse_entries, token, &StackEntry::token)};
|
||||
const auto pos{std::distance(reverse_entries.begin(), it)};
|
||||
Stack result;
|
||||
result.entries.insert(result.entries.end(), entries.begin(), entries.end() - pos - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Block::Contains(Location pc) const noexcept {
|
||||
return pc >= begin && pc < end;
|
||||
}
|
||||
|
||||
Function::Function(Location start_address)
|
||||
: entrypoint{start_address}, labels{Label{
|
||||
.address{start_address},
|
||||
.block_id{0},
|
||||
.stack{},
|
||||
}} {}
|
||||
|
||||
CFG::CFG(Environment& env_, Location start_address) : env{env_} {
|
||||
functions.emplace_back(start_address);
|
||||
for (FunctionId function_id = 0; function_id < functions.size(); ++function_id) {
|
||||
while (!functions[function_id].labels.empty()) {
|
||||
Function& function{functions[function_id]};
|
||||
Label label{function.labels.back()};
|
||||
function.labels.pop_back();
|
||||
AnalyzeLabel(function_id, label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CFG::AnalyzeLabel(FunctionId function_id, Label& label) {
|
||||
if (InspectVisitedBlocks(function_id, label)) {
|
||||
// Label address has been visited
|
||||
return;
|
||||
}
|
||||
// Try to find the next block
|
||||
Function* function{&functions[function_id]};
|
||||
Location pc{label.address};
|
||||
const auto next{std::upper_bound(function->blocks.begin(), function->blocks.end(), pc,
|
||||
[function](Location pc, u32 block_index) {
|
||||
return pc < function->blocks_data[block_index].begin;
|
||||
})};
|
||||
const auto next_index{std::distance(function->blocks.begin(), next)};
|
||||
const bool is_last{next == function->blocks.end()};
|
||||
Location next_pc;
|
||||
BlockId next_id{UNREACHABLE_BLOCK_ID};
|
||||
if (!is_last) {
|
||||
next_pc = function->blocks_data[*next].begin;
|
||||
next_id = function->blocks_data[*next].id;
|
||||
}
|
||||
// Insert before the next block
|
||||
Block block{
|
||||
.begin{pc},
|
||||
.end{pc},
|
||||
.end_class{EndClass::Branch},
|
||||
.id{label.block_id},
|
||||
.stack{std::move(label.stack)},
|
||||
.cond{true},
|
||||
.branch_true{UNREACHABLE_BLOCK_ID},
|
||||
.branch_false{UNREACHABLE_BLOCK_ID},
|
||||
};
|
||||
// Analyze instructions until it reaches an already visited block or there's a branch
|
||||
bool is_branch{false};
|
||||
while (is_last || pc < next_pc) {
|
||||
is_branch = AnalyzeInst(block, function_id, pc) == AnalysisState::Branch;
|
||||
if (is_branch) {
|
||||
break;
|
||||
}
|
||||
++pc;
|
||||
}
|
||||
if (!is_branch) {
|
||||
// If the block finished without a branch,
|
||||
// it means that the next instruction is already visited, jump to it
|
||||
block.end = pc;
|
||||
block.cond = true;
|
||||
block.branch_true = next_id;
|
||||
block.branch_false = UNREACHABLE_BLOCK_ID;
|
||||
}
|
||||
// Function's pointer might be invalid, resolve it again
|
||||
function = &functions[function_id];
|
||||
const u32 new_block_index = static_cast<u32>(function->blocks_data.size());
|
||||
function->blocks.insert(function->blocks.begin() + next_index, new_block_index);
|
||||
function->blocks_data.push_back(std::move(block));
|
||||
}
|
||||
|
||||
bool CFG::InspectVisitedBlocks(FunctionId function_id, const Label& label) {
|
||||
const Location pc{label.address};
|
||||
Function& function{functions[function_id]};
|
||||
const auto it{std::ranges::find_if(function.blocks, [&function, pc](u32 block_index) {
|
||||
return function.blocks_data[block_index].Contains(pc);
|
||||
})};
|
||||
if (it == function.blocks.end()) {
|
||||
// Address has not been visited
|
||||
return false;
|
||||
}
|
||||
Block& block{function.blocks_data[*it]};
|
||||
if (block.begin == pc) {
|
||||
throw LogicError("Dangling branch");
|
||||
}
|
||||
const u32 first_index{*it};
|
||||
const u32 second_index{static_cast<u32>(function.blocks_data.size())};
|
||||
const std::array new_indices{first_index, second_index};
|
||||
std::array split_blocks{Split(std::move(block), pc, label.block_id)};
|
||||
function.blocks_data[*it] = std::move(split_blocks[0]);
|
||||
function.blocks_data.push_back(std::move(split_blocks[1]));
|
||||
function.blocks.insert(function.blocks.erase(it), new_indices.begin(), new_indices.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Location pc) {
|
||||
const Instruction inst{env.ReadInstruction(pc.Offset())};
|
||||
const Opcode opcode{Decode(inst.raw)};
|
||||
switch (opcode) {
|
||||
case Opcode::BRA:
|
||||
case Opcode::BRX:
|
||||
case Opcode::JMP:
|
||||
case Opcode::JMX:
|
||||
case Opcode::RET:
|
||||
if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) {
|
||||
return AnalysisState::Continue;
|
||||
}
|
||||
switch (opcode) {
|
||||
case Opcode::BRA:
|
||||
case Opcode::JMP:
|
||||
AnalyzeBRA(block, function_id, pc, inst, IsAbsoluteJump(opcode));
|
||||
break;
|
||||
case Opcode::BRX:
|
||||
case Opcode::JMX:
|
||||
AnalyzeBRX(block, pc, inst, IsAbsoluteJump(opcode));
|
||||
break;
|
||||
case Opcode::RET:
|
||||
block.end_class = EndClass::Return;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
block.end = pc;
|
||||
return AnalysisState::Branch;
|
||||
case Opcode::BRK:
|
||||
case Opcode::CONT:
|
||||
case Opcode::LONGJMP:
|
||||
case Opcode::SYNC: {
|
||||
if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) {
|
||||
return AnalysisState::Continue;
|
||||
}
|
||||
const auto [stack_pc, new_stack]{block.stack.Pop(OpcodeToken(opcode))};
|
||||
block.branch_true = AddLabel(block, new_stack, stack_pc, function_id);
|
||||
block.end = pc;
|
||||
return AnalysisState::Branch;
|
||||
}
|
||||
case Opcode::PBK:
|
||||
case Opcode::PCNT:
|
||||
case Opcode::PEXIT:
|
||||
case Opcode::PLONGJMP:
|
||||
case Opcode::SSY:
|
||||
block.stack.Push(OpcodeToken(opcode), BranchOffset(pc, inst));
|
||||
return AnalysisState::Continue;
|
||||
case Opcode::EXIT:
|
||||
return AnalyzeEXIT(block, function_id, pc, inst);
|
||||
case Opcode::PRET:
|
||||
throw NotImplementedException("PRET flow analysis");
|
||||
case Opcode::CAL:
|
||||
case Opcode::JCAL: {
|
||||
const bool is_absolute{IsAbsoluteJump(opcode)};
|
||||
const Location cal_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
|
||||
// Technically CAL pushes into PRET, but that's implicit in the function call for us
|
||||
// Insert the function into the list if it doesn't exist
|
||||
if (std::ranges::find(functions, cal_pc, &Function::entrypoint) == functions.end()) {
|
||||
functions.push_back(cal_pc);
|
||||
}
|
||||
// Handle CAL like a regular instruction
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const Predicate pred{inst.Pred()};
|
||||
if (pred == Predicate{true} || pred == Predicate{false}) {
|
||||
return AnalysisState::Continue;
|
||||
}
|
||||
const IR::Condition cond{static_cast<IR::Pred>(pred.index), pred.negated};
|
||||
AnalyzeCondInst(block, function_id, pc, EndClass::Branch, cond);
|
||||
return AnalysisState::Branch;
|
||||
}
|
||||
|
||||
void CFG::AnalyzeCondInst(Block& block, FunctionId function_id, Location pc,
|
||||
EndClass insn_end_class, IR::Condition cond) {
|
||||
if (block.begin != pc) {
|
||||
// If the block doesn't start in the conditional instruction
|
||||
// mark it as a label to visit it later
|
||||
block.end = pc;
|
||||
block.cond = true;
|
||||
block.branch_true = AddLabel(block, block.stack, pc, function_id);
|
||||
block.branch_false = UNREACHABLE_BLOCK_ID;
|
||||
return;
|
||||
}
|
||||
// Impersonate the visited block with a virtual block
|
||||
// Jump from this virtual to the real conditional instruction and the next instruction
|
||||
Function& function{functions[function_id]};
|
||||
const BlockId conditional_block_id{++function.current_block_id};
|
||||
function.blocks.push_back(static_cast<u32>(function.blocks_data.size()));
|
||||
Block& virtual_block{function.blocks_data.emplace_back(Block{
|
||||
.begin{}, // Virtual block
|
||||
.end{},
|
||||
.end_class{EndClass::Branch},
|
||||
.id{block.id}, // Impersonating
|
||||
.stack{block.stack},
|
||||
.cond{cond},
|
||||
.branch_true{conditional_block_id},
|
||||
.branch_false{UNREACHABLE_BLOCK_ID},
|
||||
})};
|
||||
// Set the end properties of the conditional instruction and give it a new identity
|
||||
Block& conditional_block{block};
|
||||
conditional_block.end = pc;
|
||||
conditional_block.end_class = insn_end_class;
|
||||
conditional_block.id = conditional_block_id;
|
||||
// Add a label to the instruction after the conditional instruction
|
||||
const BlockId endif_block_id{AddLabel(conditional_block, block.stack, pc + 1, function_id)};
|
||||
// Branch to the next instruction from the virtual block
|
||||
virtual_block.branch_false = endif_block_id;
|
||||
// And branch to it from the conditional instruction if it is a branch
|
||||
if (insn_end_class == EndClass::Branch) {
|
||||
conditional_block.cond = true;
|
||||
conditional_block.branch_true = endif_block_id;
|
||||
conditional_block.branch_false = UNREACHABLE_BLOCK_ID;
|
||||
}
|
||||
}
|
||||
|
||||
bool CFG::AnalyzeBranch(Block& block, FunctionId function_id, Location pc, Instruction inst,
|
||||
Opcode opcode) {
|
||||
if (inst.branch.is_cbuf) {
|
||||
throw NotImplementedException("Branch with constant buffer offset");
|
||||
}
|
||||
const Predicate pred{inst.Pred()};
|
||||
if (pred == Predicate{false}) {
|
||||
return false;
|
||||
}
|
||||
const bool has_flow_test{HasFlowTest(opcode)};
|
||||
const IR::FlowTest flow_test{has_flow_test ? inst.branch.flow_test.Value() : IR::FlowTest::T};
|
||||
if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
|
||||
block.cond = IR::Condition(flow_test, static_cast<IR::Pred>(pred.index), pred.negated);
|
||||
block.branch_false = AddLabel(block, block.stack, pc + 1, function_id);
|
||||
} else {
|
||||
block.cond = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CFG::AnalyzeBRA(Block& block, FunctionId function_id, Location pc, Instruction inst,
|
||||
bool is_absolute) {
|
||||
const Location bra_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
|
||||
block.branch_true = AddLabel(block, block.stack, bra_pc, function_id);
|
||||
}
|
||||
|
||||
void CFG::AnalyzeBRX(Block&, Location, Instruction, bool is_absolute) {
|
||||
throw NotImplementedException("{}", is_absolute ? "JMX" : "BRX");
|
||||
}
|
||||
|
||||
void CFG::AnalyzeCAL(Location pc, Instruction inst, bool is_absolute) {
|
||||
const Location cal_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
|
||||
// Technically CAL pushes into PRET, but that's implicit in the function call for us
|
||||
// Insert the function to the function list if it doesn't exist
|
||||
const auto it{std::ranges::find(functions, cal_pc, &Function::entrypoint)};
|
||||
if (it == functions.end()) {
|
||||
functions.emplace_back(cal_pc);
|
||||
}
|
||||
}
|
||||
|
||||
CFG::AnalysisState CFG::AnalyzeEXIT(Block& block, FunctionId function_id, Location pc,
|
||||
Instruction inst) {
|
||||
const IR::FlowTest flow_test{inst.branch.flow_test};
|
||||
const Predicate pred{inst.Pred()};
|
||||
if (pred == Predicate{false} || flow_test == IR::FlowTest::F) {
|
||||
// EXIT will never be taken
|
||||
return AnalysisState::Continue;
|
||||
}
|
||||
if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
|
||||
if (block.stack.Peek(Token::PEXIT).has_value()) {
|
||||
throw NotImplementedException("Conditional EXIT with PEXIT token");
|
||||
}
|
||||
const IR::Condition cond{flow_test, static_cast<IR::Pred>(pred.index), pred.negated};
|
||||
AnalyzeCondInst(block, function_id, pc, EndClass::Exit, cond);
|
||||
return AnalysisState::Branch;
|
||||
}
|
||||
if (const std::optional<Location> exit_pc{block.stack.Peek(Token::PEXIT)}) {
|
||||
const Stack popped_stack{block.stack.Remove(Token::PEXIT)};
|
||||
block.cond = true;
|
||||
block.branch_true = AddLabel(block, popped_stack, *exit_pc, function_id);
|
||||
block.branch_false = UNREACHABLE_BLOCK_ID;
|
||||
return AnalysisState::Branch;
|
||||
}
|
||||
block.end = pc;
|
||||
block.end_class = EndClass::Exit;
|
||||
return AnalysisState::Branch;
|
||||
}
|
||||
|
||||
BlockId CFG::AddLabel(const Block& block, Stack stack, Location pc, FunctionId function_id) {
|
||||
Function& function{functions[function_id]};
|
||||
if (block.begin == pc) {
|
||||
return block.id;
|
||||
}
|
||||
const auto target{std::ranges::find(function.blocks_data, pc, &Block::begin)};
|
||||
if (target != function.blocks_data.end()) {
|
||||
return target->id;
|
||||
}
|
||||
const BlockId block_id{++function.current_block_id};
|
||||
function.labels.push_back(Label{
|
||||
.address{pc},
|
||||
.block_id{block_id},
|
||||
.stack{std::move(stack)},
|
||||
});
|
||||
return block_id;
|
||||
}
|
||||
|
||||
std::string CFG::Dot() const {
|
||||
int node_uid{0};
|
||||
|
||||
std::string dot{"digraph shader {\n"};
|
||||
for (const Function& function : functions) {
|
||||
dot += fmt::format("\tsubgraph cluster_{} {{\n", function.entrypoint);
|
||||
dot += fmt::format("\t\tnode [style=filled];\n");
|
||||
for (const u32 block_index : function.blocks) {
|
||||
const Block& block{function.blocks_data[block_index]};
|
||||
const std::string name{Name(block)};
|
||||
const auto add_branch = [&](BlockId branch_id, bool add_label) {
|
||||
const auto it{std::ranges::find(function.blocks_data, branch_id, &Block::id)};
|
||||
dot += fmt::format("\t\t{}->", name);
|
||||
if (it == function.blocks_data.end()) {
|
||||
dot += fmt::format("\"Unknown label {}\"", branch_id);
|
||||
} else {
|
||||
dot += Name(*it);
|
||||
};
|
||||
if (add_label && block.cond != true && block.cond != false) {
|
||||
dot += fmt::format(" [label=\"{}\"]", block.cond);
|
||||
}
|
||||
dot += '\n';
|
||||
};
|
||||
dot += fmt::format("\t\t{};\n", name);
|
||||
switch (block.end_class) {
|
||||
case EndClass::Branch:
|
||||
if (block.cond != false) {
|
||||
add_branch(block.branch_true, true);
|
||||
}
|
||||
if (block.cond != true) {
|
||||
add_branch(block.branch_false, false);
|
||||
}
|
||||
break;
|
||||
case EndClass::Exit:
|
||||
dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
|
||||
dot += fmt::format("\t\tN{} [label=\"Exit\"][shape=square][style=stripped];\n",
|
||||
node_uid);
|
||||
++node_uid;
|
||||
break;
|
||||
case EndClass::Return:
|
||||
dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
|
||||
dot += fmt::format("\t\tN{} [label=\"Return\"][shape=square][style=stripped];\n",
|
||||
node_uid);
|
||||
++node_uid;
|
||||
break;
|
||||
case EndClass::Unreachable:
|
||||
dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
|
||||
dot += fmt::format(
|
||||
"\t\tN{} [label=\"Unreachable\"][shape=square][style=stripped];\n", node_uid);
|
||||
++node_uid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (function.entrypoint == 8) {
|
||||
dot += fmt::format("\t\tlabel = \"main\";\n");
|
||||
} else {
|
||||
dot += fmt::format("\t\tlabel = \"Function {}\";\n", function.entrypoint);
|
||||
}
|
||||
dot += "\t}\n";
|
||||
}
|
||||
if (!functions.empty()) {
|
||||
if (functions.front().blocks.empty()) {
|
||||
dot += "Start;\n";
|
||||
} else {
|
||||
dot += fmt::format("\tStart -> {};\n", Name(functions.front().blocks_data.front()));
|
||||
}
|
||||
dot += fmt::format("\tStart [shape=diamond];\n");
|
||||
}
|
||||
dot += "}\n";
|
||||
return dot;
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell::Flow
|
|
@ -0,0 +1,137 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <compare>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "shader_recompiler/environment.h"
|
||||
#include "shader_recompiler/frontend/ir/condition.h"
|
||||
#include "shader_recompiler/frontend/maxwell/instruction.h"
|
||||
#include "shader_recompiler/frontend/maxwell/location.h"
|
||||
#include "shader_recompiler/frontend/maxwell/opcode.h"
|
||||
|
||||
namespace Shader::Maxwell::Flow {
|
||||
|
||||
using BlockId = u32;
|
||||
using FunctionId = size_t;
|
||||
|
||||
constexpr BlockId UNREACHABLE_BLOCK_ID{static_cast<u32>(-1)};
|
||||
|
||||
enum class EndClass {
|
||||
Branch,
|
||||
Exit,
|
||||
Return,
|
||||
Unreachable,
|
||||
};
|
||||
|
||||
enum class Token {
|
||||
SSY,
|
||||
PBK,
|
||||
PEXIT,
|
||||
PRET,
|
||||
PCNT,
|
||||
PLONGJMP,
|
||||
};
|
||||
|
||||
struct StackEntry {
|
||||
auto operator<=>(const StackEntry&) const noexcept = default;
|
||||
|
||||
Token token;
|
||||
Location target;
|
||||
};
|
||||
|
||||
class Stack {
|
||||
public:
|
||||
void Push(Token token, Location target);
|
||||
[[nodiscard]] std::pair<Location, Stack> Pop(Token token) const;
|
||||
[[nodiscard]] std::optional<Location> Peek(Token token) const;
|
||||
[[nodiscard]] Stack Remove(Token token) const;
|
||||
|
||||
private:
|
||||
boost::container::small_vector<StackEntry, 3> entries;
|
||||
};
|
||||
|
||||
struct Block {
|
||||
[[nodiscard]] bool Contains(Location pc) const noexcept;
|
||||
|
||||
Location begin;
|
||||
Location end;
|
||||
EndClass end_class;
|
||||
BlockId id;
|
||||
Stack stack;
|
||||
IR::Condition cond;
|
||||
BlockId branch_true;
|
||||
BlockId branch_false;
|
||||
};
|
||||
|
||||
struct Label {
|
||||
Location address;
|
||||
BlockId block_id;
|
||||
Stack stack;
|
||||
};
|
||||
|
||||
struct Function {
|
||||
Function(Location start_address);
|
||||
|
||||
Location entrypoint;
|
||||
BlockId current_block_id{0};
|
||||
boost::container::small_vector<Label, 16> labels;
|
||||
boost::container::small_vector<u32, 0x130> blocks;
|
||||
boost::container::small_vector<Block, 0x130> blocks_data;
|
||||
};
|
||||
|
||||
class CFG {
|
||||
enum class AnalysisState {
|
||||
Branch,
|
||||
Continue,
|
||||
};
|
||||
|
||||
public:
|
||||
explicit CFG(Environment& env, Location start_address);
|
||||
|
||||
[[nodiscard]] std::string Dot() const;
|
||||
|
||||
[[nodiscard]] std::span<const Function> Functions() const noexcept {
|
||||
return std::span(functions.data(), functions.size());
|
||||
}
|
||||
|
||||
private:
|
||||
void AnalyzeLabel(FunctionId function_id, Label& label);
|
||||
|
||||
/// Inspect already visited blocks.
|
||||
/// Return true when the block has already been visited
|
||||
[[nodiscard]] bool InspectVisitedBlocks(FunctionId function_id, const Label& label);
|
||||
|
||||
[[nodiscard]] AnalysisState AnalyzeInst(Block& block, FunctionId function_id, Location pc);
|
||||
|
||||
void AnalyzeCondInst(Block& block, FunctionId function_id, Location pc, EndClass insn_end_class,
|
||||
IR::Condition cond);
|
||||
|
||||
/// Return true when the branch instruction is confirmed to be a branch
|
||||
[[nodiscard]] bool AnalyzeBranch(Block& block, FunctionId function_id, Location pc,
|
||||
Instruction inst, Opcode opcode);
|
||||
|
||||
void AnalyzeBRA(Block& block, FunctionId function_id, Location pc, Instruction inst,
|
||||
bool is_absolute);
|
||||
void AnalyzeBRX(Block& block, Location pc, Instruction inst, bool is_absolute);
|
||||
void AnalyzeCAL(Location pc, Instruction inst, bool is_absolute);
|
||||
AnalysisState AnalyzeEXIT(Block& block, FunctionId function_id, Location pc, Instruction inst);
|
||||
|
||||
/// Return the branch target block id
|
||||
[[nodiscard]] BlockId AddLabel(const Block& block, Stack stack, Location pc,
|
||||
FunctionId function_id);
|
||||
|
||||
Environment& env;
|
||||
boost::container::small_vector<Function, 1> functions;
|
||||
FunctionId current_function_id{0};
|
||||
};
|
||||
|
||||
} // namespace Shader::Maxwell::Flow
|
|
@ -0,0 +1,149 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/maxwell/decode.h"
|
||||
#include "shader_recompiler/frontend/maxwell/opcode.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
namespace {
|
||||
struct MaskValue {
|
||||
u64 mask;
|
||||
u64 value;
|
||||
};
|
||||
|
||||
constexpr MaskValue MaskValueFromEncoding(const char* encoding) {
|
||||
u64 mask{};
|
||||
u64 value{};
|
||||
u64 bit{u64(1) << 63};
|
||||
while (*encoding) {
|
||||
switch (*encoding) {
|
||||
case '0':
|
||||
mask |= bit;
|
||||
break;
|
||||
case '1':
|
||||
mask |= bit;
|
||||
value |= bit;
|
||||
break;
|
||||
case '-':
|
||||
break;
|
||||
case ' ':
|
||||
break;
|
||||
default:
|
||||
throw LogicError("Invalid encoding character '{}'", *encoding);
|
||||
}
|
||||
++encoding;
|
||||
if (*encoding != ' ') {
|
||||
bit >>= 1;
|
||||
}
|
||||
}
|
||||
return MaskValue{.mask{mask}, .value{value}};
|
||||
}
|
||||
|
||||
struct InstEncoding {
|
||||
MaskValue mask_value;
|
||||
Opcode opcode;
|
||||
};
|
||||
constexpr std::array UNORDERED_ENCODINGS{
|
||||
#define INST(name, cute, encode) \
|
||||
InstEncoding{ \
|
||||
.mask_value{MaskValueFromEncoding(encode)}, \
|
||||
.opcode{Opcode::name}, \
|
||||
},
|
||||
#include "maxwell.inc"
|
||||
#undef INST
|
||||
};
|
||||
|
||||
constexpr auto SortedEncodings() {
|
||||
std::array encodings{UNORDERED_ENCODINGS};
|
||||
std::ranges::sort(encodings, [](const InstEncoding& lhs, const InstEncoding& rhs) {
|
||||
return std::popcount(lhs.mask_value.mask) > std::popcount(rhs.mask_value.mask);
|
||||
});
|
||||
return encodings;
|
||||
}
|
||||
constexpr auto ENCODINGS{SortedEncodings()};
|
||||
|
||||
constexpr int WidestLeftBits() {
|
||||
int bits{64};
|
||||
for (const InstEncoding& encoding : ENCODINGS) {
|
||||
bits = std::min(bits, std::countr_zero(encoding.mask_value.mask));
|
||||
}
|
||||
return 64 - bits;
|
||||
}
|
||||
constexpr int WIDEST_LEFT_BITS{WidestLeftBits()};
|
||||
constexpr int MASK_SHIFT{64 - WIDEST_LEFT_BITS};
|
||||
|
||||
constexpr size_t ToFastLookupIndex(u64 value) {
|
||||
return static_cast<size_t>(value >> MASK_SHIFT);
|
||||
}
|
||||
|
||||
constexpr size_t FastLookupSize() {
|
||||
size_t max_width{};
|
||||
for (const InstEncoding& encoding : ENCODINGS) {
|
||||
max_width = std::max(max_width, ToFastLookupIndex(encoding.mask_value.mask));
|
||||
}
|
||||
return max_width + 1;
|
||||
}
|
||||
constexpr size_t FAST_LOOKUP_SIZE{FastLookupSize()};
|
||||
|
||||
struct InstInfo {
|
||||
[[nodiscard]] u64 Mask() const noexcept {
|
||||
return static_cast<u64>(high_mask) << MASK_SHIFT;
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 Value() const noexcept {
|
||||
return static_cast<u64>(high_value) << MASK_SHIFT;
|
||||
}
|
||||
|
||||
u16 high_mask;
|
||||
u16 high_value;
|
||||
Opcode opcode;
|
||||
};
|
||||
|
||||
constexpr auto MakeFastLookupTableIndex(size_t index) {
|
||||
std::array<InstInfo, 2> encodings{};
|
||||
size_t element{};
|
||||
for (const auto& encoding : ENCODINGS) {
|
||||
const size_t mask{ToFastLookupIndex(encoding.mask_value.mask)};
|
||||
const size_t value{ToFastLookupIndex(encoding.mask_value.value)};
|
||||
if ((index & mask) == value) {
|
||||
encodings.at(element) = InstInfo{
|
||||
.high_mask{static_cast<u16>(encoding.mask_value.mask >> MASK_SHIFT)},
|
||||
.high_value{static_cast<u16>(encoding.mask_value.value >> MASK_SHIFT)},
|
||||
.opcode{encoding.opcode},
|
||||
};
|
||||
++element;
|
||||
}
|
||||
}
|
||||
return encodings;
|
||||
}
|
||||
|
||||
/*constexpr*/ auto MakeFastLookupTable() {
|
||||
auto encodings{std::make_unique<std::array<std::array<InstInfo, 2>, FAST_LOOKUP_SIZE>>()};
|
||||
for (size_t index = 0; index < FAST_LOOKUP_SIZE; ++index) {
|
||||
(*encodings)[index] = MakeFastLookupTableIndex(index);
|
||||
}
|
||||
return encodings;
|
||||
}
|
||||
const auto FAST_LOOKUP_TABLE{MakeFastLookupTable()};
|
||||
} // Anonymous namespace
|
||||
|
||||
Opcode Decode(u64 insn) {
|
||||
const auto& table{(*FAST_LOOKUP_TABLE)[ToFastLookupIndex(insn)]};
|
||||
const auto it{std::ranges::find_if(
|
||||
table, [insn](const InstInfo& info) { return (insn & info.Mask()) == info.Value(); })};
|
||||
if (it == table.end()) {
|
||||
throw NotImplementedException("Instruction 0x{:016x} is unknown / unimplemented", insn);
|
||||
}
|
||||
return it->opcode;
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/frontend/maxwell/opcode.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
[[nodiscard]] Opcode Decode(u64 insn);
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/frontend/ir/flow_test.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
struct Predicate {
|
||||
Predicate() = default;
|
||||
Predicate(unsigned index_, bool negated_ = false) : index{index_}, negated{negated_} {}
|
||||
Predicate(bool value) : index{7}, negated{!value} {}
|
||||
Predicate(u64 raw) : index{static_cast<unsigned>(raw & 7)}, negated{(raw & 8) != 0} {}
|
||||
|
||||
unsigned index;
|
||||
bool negated;
|
||||
};
|
||||
|
||||
inline bool operator==(const Predicate& lhs, const Predicate& rhs) noexcept {
|
||||
return lhs.index == rhs.index && lhs.negated == rhs.negated;
|
||||
}
|
||||
|
||||
inline bool operator!=(const Predicate& lhs, const Predicate& rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
union Instruction {
|
||||
Instruction(u64 raw_) : raw{raw_} {}
|
||||
|
||||
u64 raw;
|
||||
|
||||
union {
|
||||
BitField<5, 1, u64> is_cbuf;
|
||||
BitField<0, 5, IR::FlowTest> flow_test;
|
||||
|
||||
[[nodiscard]] u32 Absolute() const noexcept {
|
||||
return static_cast<u32>(absolute);
|
||||
}
|
||||
|
||||
[[nodiscard]] s32 Offset() const noexcept {
|
||||
return static_cast<s32>(offset);
|
||||
}
|
||||
|
||||
private:
|
||||
BitField<20, 24, s64> offset;
|
||||
BitField<20, 32, u64> absolute;
|
||||
} branch;
|
||||
|
||||
[[nodiscard]] Predicate Pred() const noexcept {
|
||||
return Predicate{pred};
|
||||
}
|
||||
|
||||
private:
|
||||
BitField<16, 4, u64> pred;
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<Instruction>);
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <compare>
|
||||
#include <iterator>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
class Location {
|
||||
static constexpr u32 VIRTUAL_OFFSET{std::numeric_limits<u32>::max()};
|
||||
|
||||
public:
|
||||
constexpr Location() = default;
|
||||
|
||||
constexpr Location(u32 initial_offset) : offset{initial_offset} {
|
||||
if (initial_offset % 8 != 0) {
|
||||
throw InvalidArgument("initial_offset={} is not a multiple of 8", initial_offset);
|
||||
}
|
||||
Align();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u32 Offset() const noexcept {
|
||||
return offset;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool IsVirtual() const {
|
||||
return offset == VIRTUAL_OFFSET;
|
||||
}
|
||||
|
||||
constexpr auto operator<=>(const Location&) const noexcept = default;
|
||||
|
||||
constexpr Location operator++() noexcept {
|
||||
const Location copy{*this};
|
||||
Step();
|
||||
return copy;
|
||||
}
|
||||
|
||||
constexpr Location operator++(int) noexcept {
|
||||
Step();
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Location operator--() noexcept {
|
||||
const Location copy{*this};
|
||||
Back();
|
||||
return copy;
|
||||
}
|
||||
|
||||
constexpr Location operator--(int) noexcept {
|
||||
Back();
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Location operator+(int number) const {
|
||||
Location new_pc{*this};
|
||||
while (number > 0) {
|
||||
--number;
|
||||
++new_pc;
|
||||
}
|
||||
while (number < 0) {
|
||||
++number;
|
||||
--new_pc;
|
||||
}
|
||||
return new_pc;
|
||||
}
|
||||
|
||||
constexpr Location operator-(int number) const {
|
||||
return operator+(-number);
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr void Align() {
|
||||
offset += offset % 32 == 0 ? 8 : 0;
|
||||
}
|
||||
|
||||
constexpr void Step() {
|
||||
offset += 8 + (offset % 32 == 24 ? 8 : 0);
|
||||
}
|
||||
|
||||
constexpr void Back() {
|
||||
offset -= 8 + (offset % 32 == 8 ? 8 : 0);
|
||||
}
|
||||
|
||||
u32 offset{VIRTUAL_OFFSET};
|
||||
};
|
||||
|
||||
} // namespace Shader::Maxwell
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Shader::Maxwell::Location> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const Shader::Maxwell::Location& location, FormatContext& ctx) {
|
||||
return fmt::format_to(ctx.out(), "{:04x}", location.Offset());
|
||||
}
|
||||
};
|
|
@ -0,0 +1,285 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
INST(AL2P, "AL2P", "1110 1111 1010 0---")
|
||||
INST(ALD, "ALD", "1110 1111 1101 1---")
|
||||
INST(AST, "AST", "1110 1111 1111 0---")
|
||||
INST(ATOM_cas, "ATOM (cas)", "1110 1110 1111 ----")
|
||||
INST(ATOM, "ATOM", "1110 1101 ---- ----")
|
||||
INST(ATOMS_cas, "ATOMS (cas)", "1110 1110 ---- ----")
|
||||
INST(ATOMS, "ATOMS", "1110 1100 ---- ----")
|
||||
INST(B2R, "B2R", "1111 0000 1011 1---")
|
||||
INST(BAR, "BAR", "1111 0000 1010 1---")
|
||||
INST(BFE_reg, "BFE (reg)", "0101 1100 0000 0---")
|
||||
INST(BFE_cbuf, "BFE (cbuf)", "0100 1100 0000 0---")
|
||||
INST(BFE_imm, "BFE (imm)", "0011 100- 0000 0---")
|
||||
INST(BFI_reg, "BFI (reg)", "0101 1011 1111 0---")
|
||||
INST(BFI_rc, "BFI (rc)", "0101 0011 1111 0---")
|
||||
INST(BFI_cr, "BFI (cr)", "0100 1011 1111 0---")
|
||||
INST(BFI_imm, "BFI (imm)", "0011 011- 1111 0---")
|
||||
INST(BPT, "BPT", "1110 0011 1010 ----")
|
||||
INST(BRA, "BRA", "1110 0010 0100 ----")
|
||||
INST(BRK, "BRK", "1110 0011 0100 ----")
|
||||
INST(BRX, "BRX", "1110 0010 0101 ----")
|
||||
INST(CAL, "CAL", "1110 0010 0110 ----")
|
||||
INST(CCTL, "CCTL", "1110 1111 011- ----")
|
||||
INST(CCTLL, "CCTLL", "1110 1111 100- ----")
|
||||
INST(CONT, "CONT", "1110 0011 0101 ----")
|
||||
INST(CS2R, "CS2R", "0101 0000 1100 1---")
|
||||
INST(CSET, "CSET", "0101 0000 1001 1---")
|
||||
INST(CSETP, "CSETP", "0101 0000 1010 0---")
|
||||
INST(DADD_reg, "DADD (reg)", "0101 1100 0111 0---")
|
||||
INST(DADD_cbuf, "DADD (cbuf)", "0100 1100 0111 0---")
|
||||
INST(DADD_imm, "DADD (imm)", "0011 100- 0111 0---")
|
||||
INST(DEPBAR, "DEPBAR", "1111 0000 1111 0---")
|
||||
INST(DFMA_reg, "DFMA (reg)", "0101 1011 0111 ----")
|
||||
INST(DFMA_rc, "DFMA (rc)", "0101 0011 0111 ----")
|
||||
INST(DFMA_cr, "DFMA (cr)", "0010 1011 0111 ----")
|
||||
INST(DFMA_imm, "DFMA (imm)", "0011 011- 0111 ----")
|
||||
INST(DMNMX_reg, "DMNMX (reg)", "0100 1100 0101 0---")
|
||||
INST(DMNMX_cbuf, "DMNMX (cbuf)", "0101 1100 0101 0---")
|
||||
INST(DMNMX_imm, "DMNMX (imm)", "0011 100- 0101 0---")
|
||||
INST(DMUL_reg, "DMUL (reg)", "0101 1100 1000 0---")
|
||||
INST(DMUL_cbuf, "DMUL (cbuf)", "0100 1100 1000 0---")
|
||||
INST(DMUL_imm, "DMUL (imm)", "0011 100- 1000 0---")
|
||||
INST(DSET_reg, "DSET (reg)", "0101 1001 0--- ----")
|
||||
INST(DSET_cbuf, "DSET (cbuf)", "0100 1001 0--- ----")
|
||||
INST(DSET_imm, "DSET (imm)", "0011 001- 0--- ----")
|
||||
INST(DSETP_reg, "DSETP (reg)", "0101 1011 1000 ----")
|
||||
INST(DSETP_cbuf, "DSETP (cbuf)", "0100 1011 1000 ----")
|
||||
INST(DSETP_imm, "DSETP (imm)", "0011 011- 1000 ----")
|
||||
INST(EXIT, "EXIT", "1110 0011 0000 ----")
|
||||
INST(F2F_reg, "F2F (reg)", "0101 1100 1010 1---")
|
||||
INST(F2F_cbuf, "F2F (cbuf)", "0100 1100 1010 1---")
|
||||
INST(F2F_imm, "F2F (imm)", "0011 100- 1010 1---")
|
||||
INST(F2I_reg, "F2I (reg)", "0101 1100 1011 0---")
|
||||
INST(F2I_cbuf, "F2I (cbuf)", "0100 1100 1011 0---")
|
||||
INST(F2I_imm, "F2I (imm)", "0011 100- 1011 0---")
|
||||
INST(FADD_reg, "FADD (reg)", "0101 1100 0101 1---")
|
||||
INST(FADD_cbuf, "FADD (cbuf)", "0100 1100 0101 1---")
|
||||
INST(FADD_imm, "FADD (imm)", "0011 100- 0101 1---")
|
||||
INST(FADD32I, "FADD32I", "0000 10-- ---- ----")
|
||||
INST(FCHK_reg, "FCHK (reg)", "0101 1100 1000 1---")
|
||||
INST(FCHK_cbuf, "FCHK (cbuf)", "0100 1100 1000 1---")
|
||||
INST(FCHK_imm, "FCHK (imm)", "0011 100- 1000 1---")
|
||||
INST(FCMP_reg, "FCMP (reg)", "0101 1011 1010 ----")
|
||||
INST(FCMP_rc, "FCMP (rc)", "0101 0011 1010 ----")
|
||||
INST(FCMP_cr, "FCMP (cr)", "0100 1011 1010 ----")
|
||||
INST(FCMP_imm, "FCMP (imm)", "0011 011- 1010 ----")
|
||||
INST(FFMA_reg, "FFMA (reg)", "0101 1001 1--- ----")
|
||||
INST(FFMA_rc, "FFMA (rc)", "0101 0001 1--- ----")
|
||||
INST(FFMA_cr, "FFMA (cr)", "0100 1001 1--- ----")
|
||||
INST(FFMA_imm, "FFMA (imm)", "0011 001- 1--- ----")
|
||||
INST(FFMA32I, "FFMA32I", "0000 11-- ---- ----")
|
||||
INST(FLO_reg, "FLO (reg)", "0101 1100 0011 0---")
|
||||
INST(FLO_cbuf, "FLO (cbuf)", "0100 1100 0011 0---")
|
||||
INST(FLO_imm, "FLO (imm)", "0011 100- 0011 0---")
|
||||
INST(FMNMX_reg, "FMNMX (reg)", "0101 1100 0110 0---")
|
||||
INST(FMNMX_cbuf, "FMNMX (cbuf)", "0100 1100 0110 0---")
|
||||
INST(FMNMX_imm, "FMNMX (imm)", "0011 100- 0110 0---")
|
||||
INST(FMUL_reg, "FMUL (reg)", "0101 1100 0110 1---")
|
||||
INST(FMUL_cbuf, "FMUL (cbuf)", "0100 1100 0110 1---")
|
||||
INST(FMUL_imm, "FMUL (imm)", "0011 100- 0110 1---")
|
||||
INST(FMUL32I, "FMUL32I", "0001 1110 ---- ----")
|
||||
INST(FSET_reg, "FSET (reg)", "0101 1000 ---- ----")
|
||||
INST(FSET_cbuf, "FSET (cbuf)", "0100 1000 ---- ----")
|
||||
INST(FSET_imm, "FSET (imm)", "0011 000- ---- ----")
|
||||
INST(FSETP_reg, "FSETP (reg)", "0101 1011 1011 ----")
|
||||
INST(FSETP_cbuf, "FSETP (cbuf)", "0100 1011 1011 ----")
|
||||
INST(FSETP_imm, "FSETP (imm)", "0011 011- 1011 ----")
|
||||
INST(FSWZADD, "FSWZADD", "0101 0000 1111 1---")
|
||||
INST(GETCRSPTR, "GETCRSPTR", "1110 0010 1100 ----")
|
||||
INST(GETLMEMBASE, "GETLMEMBASE", "1110 0010 1101 ----")
|
||||
INST(HADD2_reg, "HADD2 (reg)", "0101 1101 0001 0---")
|
||||
INST(HADD2_cbuf, "HADD2 (cbuf)", "0111 101- 1--- ----")
|
||||
INST(HADD2_imm, "HADD2 (imm)", "0111 101- 0--- ----")
|
||||
INST(HADD2_32I, "HADD2_32I", "0010 110- ---- ----")
|
||||
INST(HFMA2_reg, "HFMA2 (reg)", "0101 1101 0000 0---")
|
||||
INST(HFMA2_rc, "HFMA2 (rc)", "0110 0--- 1--- ----")
|
||||
INST(HFMA2_cr, "HFMA2 (cr)", "0111 0--- 1--- ----")
|
||||
INST(HFMA2_imm, "HFMA2 (imm)", "0111 0--- 0--- ----")
|
||||
INST(HFMA2_32I, "HFMA2_32I", "0010 100- ---- ----")
|
||||
INST(HMUL2_reg, "HMUL2 (reg)", "0101 1101 0000 1---")
|
||||
INST(HMUL2_cbuf, "HMUL2 (cbuf)", "0111 100- 1--- ----")
|
||||
INST(HMUL2_imm, "HMUL2 (imm)", "0111 100- 0--- ----")
|
||||
INST(HMUL2_32I, "HMUL2_32I", "0010 101- ---- ----")
|
||||
INST(HSET2_reg, "HSET2 (reg)", "0101 1101 0001 1---")
|
||||
INST(HSET2_cbuf, "HSET2 (cbuf)", "0111 1100 1--- ----")
|
||||
INST(HSET2_imm, "HSET2 (imm)", "0111 1100 0--- ----")
|
||||
INST(HSETP2_reg, "HSETP2 (reg)", "0101 1101 0010 0---")
|
||||
INST(HSETP2_cbuf, "HSETP2 (cbuf)", "0111 111- 1--- ----")
|
||||
INST(HSETP2_imm, "HSETP2 (imm)", "0111 111- 0--- ----")
|
||||
INST(I2F_reg, "I2F (reg)", "0101 1100 1011 1---")
|
||||
INST(I2F_cbuf, "I2F (cbuf)", "0100 1100 1011 1---")
|
||||
INST(I2F_imm, "I2F (imm)", "0011 100- 1011 1---")
|
||||
INST(I2I_reg, "I2I (reg)", "0101 1100 1110 0---")
|
||||
INST(I2I_cbuf, "I2I (cbuf)", "0100 1100 1110 0---")
|
||||
INST(I2I_imm, "I2I (imm)", "0011 100- 1110 0---")
|
||||
INST(IADD_reg, "IADD (reg)", "0101 1100 0001 0---")
|
||||
INST(IADD_cbuf, "IADD (cbuf)", "0100 1100 0001 0---")
|
||||
INST(IADD_imm, "IADD (imm)", "0011 100- 0001 0---")
|
||||
INST(IADD3_reg, "IADD3 (reg)", "0101 1100 1100 ----")
|
||||
INST(IADD3_cbuf, "IADD3 (cbuf)", "0100 1100 1100 ----")
|
||||
INST(IADD3_imm, "IADD3 (imm)", "0011 100- 1100 ----")
|
||||
INST(IADD32I, "IADD32I", "0001 110- ---- ----")
|
||||
INST(ICMP_reg, "ICMP (reg)", "0101 1011 0100 ----")
|
||||
INST(ICMP_rc, "ICMP (rc)", "0101 0011 0100 ----")
|
||||
INST(ICMP_cr, "ICMP (cr)", "0100 1011 0100 ----")
|
||||
INST(ICMP_imm, "ICMP (imm)", "0011 011- 0100 ----")
|
||||
INST(IDE, "IDE", "1110 0011 1001 ----")
|
||||
INST(IDP_reg, "IDP (reg)", "0101 0011 1111 1---")
|
||||
INST(IDP_imm, "IDP (imm)", "0101 0011 1101 1---")
|
||||
INST(IMAD_reg, "IMAD (reg)", "0101 1010 0--- ----")
|
||||
INST(IMAD_rc, "IMAD (rc)", "0101 0010 0--- ----")
|
||||
INST(IMAD_cr, "IMAD (cr)", "0100 1010 0--- ----")
|
||||
INST(IMAD_imm, "IMAD (imm)", "0011 010- 0--- ----")
|
||||
INST(IMAD32I, "IMAD32I", "1000 00-- ---- ----")
|
||||
INST(IMADSP_reg, "IMADSP (reg)", "0101 1010 1--- ----")
|
||||
INST(IMADSP_rc, "IMADSP (rc)", "0101 0010 1--- ----")
|
||||
INST(IMADSP_cr, "IMADSP (cr)", "0100 1010 1--- ----")
|
||||
INST(IMADSP_imm, "IMADSP (imm)", "0011 010- 1--- ----")
|
||||
INST(IMNMX_reg, "IMNMX (reg)", "0101 1100 0010 0---")
|
||||
INST(IMNMX_cbuf, "IMNMX (cbuf)", "0100 1100 0010 0---")
|
||||
INST(IMNMX_imm, "IMNMX (imm)", "0011 100- 0010 0---")
|
||||
INST(IMUL_reg, "IMUL (reg)", "0101 1100 0011 1---")
|
||||
INST(IMUL_cbuf, "IMUL (cbuf)", "0100 1100 0011 1---")
|
||||
INST(IMUL_imm, "IMUL (imm)", "0011 100- 0011 1---")
|
||||
INST(IMUL32I, "IMUL32I", "0001 1111 ---- ----")
|
||||
INST(IPA, "IPA", "1110 0000 ---- ----")
|
||||
INST(ISBERD, "ISBERD", "1110 1111 1101 0---")
|
||||
INST(ISCADD_reg, "ISCADD (reg)", "0101 1100 0001 1---")
|
||||
INST(ISCADD_cbuf, "ISCADD (cbuf)", "0100 1100 0001 1---")
|
||||
INST(ISCADD_imm, "ISCADD (imm)", "0011 100- 0001 1---")
|
||||
INST(ISCADD32I, "ISCADD32I", "0001 01-- ---- ----")
|
||||
INST(ISET_reg, "ISET (reg)", "0101 1011 0101 ----")
|
||||
INST(ISET_cbuf, "ISET (cbuf)", "0100 1011 0101 ----")
|
||||
INST(ISET_imm, "ISET (imm)", "0011 011- 0101 ----")
|
||||
INST(ISETP_reg, "ISETP (reg)", "0101 1011 0110 ----")
|
||||
INST(ISETP_cbuf, "ISETP (cbuf)", "0100 1011 0110 ----")
|
||||
INST(ISETP_imm, "ISETP (imm)", "0011 011- 0110 ----")
|
||||
INST(JCAL, "JCAL", "1110 0010 0010 ----")
|
||||
INST(JMP, "JMP", "1110 0010 0001 ----")
|
||||
INST(JMX, "JMX", "1110 0010 0000 ----")
|
||||
INST(KIL, "KIL", "1110 0011 0011 ----")
|
||||
INST(LD, "LD", "100- ---- ---- ----")
|
||||
INST(LDC, "LDC", "1110 1111 1001 0---")
|
||||
INST(LDG, "LDG", "1110 1110 1101 0---")
|
||||
INST(LDL, "LDL", "1110 1111 0100 0---")
|
||||
INST(LDS, "LDS", "1110 1111 0100 1---")
|
||||
INST(LEA_hi_reg, "LEA (hi reg)", "0101 1011 1101 1---")
|
||||
INST(LEA_hi_cbuf, "LEA (hi cbuf)", "0001 10-- ---- ----")
|
||||
INST(LEA_lo_reg, "LEA (lo reg)", "0101 1011 1101 0---")
|
||||
INST(LEA_lo_cbuf, "LEA (lo cbuf)", "0100 1011 1101 ----")
|
||||
INST(LEA_lo_imm, "LEA (lo imm)", "0011 011- 1101 0---")
|
||||
INST(LEPC, "LEPC", "0101 0000 1101 0---")
|
||||
INST(LONGJMP, "LONGJMP", "1110 0011 0001 ----")
|
||||
INST(LOP_reg, "LOP (reg)", "0101 1100 0100 0---")
|
||||
INST(LOP_cbuf, "LOP (cbuf)", "0100 1100 0100 0---")
|
||||
INST(LOP_imm, "LOP (imm)", "0011 100- 0100 0---")
|
||||
INST(LOP3_reg, "LOP3 (reg)", "0101 1011 1110 0---")
|
||||
INST(LOP3_cbuf, "LOP3 (cbuf)", "0011 11-- ---- ----")
|
||||
INST(LOP3_imm, "LOP3 (imm)", "0000 001- ---- ----")
|
||||
INST(LOP32I, "LOP32I", "0000 01-- ---- ----")
|
||||
INST(MEMBAR, "MEMBAR", "1110 1111 1001 1---")
|
||||
INST(MOV_reg, "MOV (reg)", "0101 1100 1001 1---")
|
||||
INST(MOV_cbuf, "MOV (cbuf)", "0100 1100 1001 1---")
|
||||
INST(MOV_imm, "MOV (imm)", "0011 100- 1001 1---")
|
||||
INST(MOV32I, "MOV32I", "0000 0001 0000 ----")
|
||||
INST(MUFU, "MUFU", "0101 0000 1000 0---")
|
||||
INST(NOP, "NOP", "0101 0000 1011 0---")
|
||||
INST(OUT_reg, "OUT (reg)", "1111 1011 1110 0---")
|
||||
INST(OUT_cbuf, "OUT (cbuf)", "1110 1011 1110 0---")
|
||||
INST(OUT_imm, "OUT (imm)", "1111 011- 1110 0---")
|
||||
INST(P2R_reg, "P2R (reg)", "0101 1100 1110 1---")
|
||||
INST(P2R_cbuf, "P2R (cbuf)", "0100 1100 1110 1---")
|
||||
INST(P2R_imm, "P2R (imm)", "0011 1000 1110 1---")
|
||||
INST(PBK, "PBK", "1110 0010 1010 ----")
|
||||
INST(PCNT, "PCNT", "1110 0010 1011 ----")
|
||||
INST(PEXIT, "PEXIT", "1110 0010 0011 ----")
|
||||
INST(PIXLD, "PIXLD", "1110 1111 1110 1---")
|
||||
INST(PLONGJMP, "PLONGJMP", "1110 0010 1000 ----")
|
||||
INST(POPC_reg, "POPC (reg)", "0101 1100 0000 1---")
|
||||
INST(POPC_cbuf, "POPC (cbuf)", "0100 1100 0000 1---")
|
||||
INST(POPC_imm, "POPC (imm)", "0011 100- 0000 1---")
|
||||
INST(PRET, "PRET", "1110 0010 0111 ----")
|
||||
INST(PRMT_reg, "PRMT (reg)", "0101 1011 1100 ----")
|
||||
INST(PRMT_rc, "PRMT (rc)", "0101 0011 1100 ----")
|
||||
INST(PRMT_cr, "PRMT (cr)", "0100 1011 1100 ----")
|
||||
INST(PRMT_imm, "PRMT (imm)", "0011 011- 1100 ----")
|
||||
INST(PSET, "PSET", "0101 0000 1000 1---")
|
||||
INST(PSETP, "PSETP", "0101 0000 1001 0---")
|
||||
INST(R2B, "R2B", "1111 0000 1100 0---")
|
||||
INST(R2P_reg, "R2P (reg)", "0101 1100 1111 0---")
|
||||
INST(R2P_cbuf, "R2P (cbuf)", "0100 1100 1111 0---")
|
||||
INST(R2P_imm, "R2P (imm)", "0011 100- 1111 0---")
|
||||
INST(RAM, "RAM", "1110 0011 1000 ----")
|
||||
INST(RED, "RED", "1110 1011 1111 1---")
|
||||
INST(RET, "RET", "1110 0011 0010 ----")
|
||||
INST(RRO_reg, "RRO (reg)", "0101 1100 1001 0---")
|
||||
INST(RRO_cbuf, "RRO (cbuf)", "0100 1100 1001 0---")
|
||||
INST(RRO_imm, "RRO (imm)", "0011 100- 1001 0---")
|
||||
INST(RTT, "RTT", "1110 0011 0110 ----")
|
||||
INST(S2R, "S2R", "1111 0000 1100 1---")
|
||||
INST(SAM, "SAM", "1110 0011 0111 ----")
|
||||
INST(SEL_reg, "SEL (reg)", "0101 1100 1010 0---")
|
||||
INST(SEL_cbuf, "SEL (cbuf)", "0100 1100 1010 0---")
|
||||
INST(SEL_imm, "SEL (imm)", "0011 100- 1010 0---")
|
||||
INST(SETCRSPTR, "SETCRSPTR", "1110 0010 1110 ----")
|
||||
INST(SETLMEMBASE, "SETLMEMBASE", "1110 0010 1111 ----")
|
||||
INST(SHF_l_reg, "SHF (l reg)", "0101 1011 1111 1---")
|
||||
INST(SHF_l_imm, "SHF (l imm)", "0011 011- 1111 1---")
|
||||
INST(SHF_r_reg, "SHF (r reg)", "0101 1100 1111 1---")
|
||||
INST(SHF_r_imm, "SHF (r imm)", "0011 100- 1111 1---")
|
||||
INST(SHFL, "SHFL", "1110 1111 0001 0---")
|
||||
INST(SHL_reg, "SHL (reg)", "0101 1100 0100 1---")
|
||||
INST(SHL_cbuf, "SHL (cbuf)", "0100 1100 0100 1---")
|
||||
INST(SHL_imm, "SHL (imm)", "0011 100- 0100 1---")
|
||||
INST(SHR_reg, "SHR (reg)", "0101 1100 0010 1---")
|
||||
INST(SHR_cbuf, "SHR (cbuf)", "0100 1100 0010 1---")
|
||||
INST(SHR_imm, "SHR (imm)", "0011 100- 0010 1---")
|
||||
INST(SSY, "SSY", "1110 0010 1001 ----")
|
||||
INST(ST, "ST", "101- ---- ---- ----")
|
||||
INST(STG, "STG", "1110 1110 1101 1---")
|
||||
INST(STL, "STL", "1110 1111 0101 0---")
|
||||
INST(STP, "STP", "1110 1110 1010 0---")
|
||||
INST(STS, "STS", "1110 1111 0101 1---")
|
||||
INST(SUATOM_cas, "SUATOM", "1110 1010 ---- ----")
|
||||
INST(SULD, "SULD", "1110 1011 000- ----")
|
||||
INST(SURED, "SURED", "1110 1011 010- ----")
|
||||
INST(SUST, "SUST", "1110 1011 001- ----")
|
||||
INST(SYNC, "SYNC", "1111 0000 1111 1---")
|
||||
INST(TEX, "TEX", "1100 00-- --11 1---")
|
||||
INST(TEX_b, "TEX (b)", "1101 1110 1011 1---")
|
||||
INST(TEXS, "TEXS", "1101 -00- ---- ----")
|
||||
INST(TLD, "TLD", "1101 1100 --11 1---")
|
||||
INST(TLD_b, "TLD (b)", "1101 1101 --11 1---")
|
||||
INST(TLD4, "TLD4", "1100 10-- --11 1---")
|
||||
INST(TLD4_b, "TLD4 (b)", "1101 1110 1111 1---")
|
||||
INST(TLD4S, "TLD4S", "1101 1111 -0-- ----")
|
||||
INST(TLDS, "TLDS", "1101 -01- ---- ----")
|
||||
INST(TMML, "TMML", "1101 1111 0101 1---")
|
||||
INST(TMML_b, "TMML (b)", "1101 1111 0110 0---")
|
||||
INST(TXA, "TXA", "1101 1111 0100 0---")
|
||||
INST(TXD, "TXD", "1101 1110 0011 10--")
|
||||
INST(TXD_b, "TXD (b)", "1101 1110 0111 10--")
|
||||
INST(TXQ, "TXQ", "1101 1111 0100 1---")
|
||||
INST(TXQ_b, "TXQ (b)", "1101 1111 0101 0---")
|
||||
INST(VABSDIFF, "VABSDIFF", "0101 0100 ---- ----")
|
||||
INST(VABSDIFF4, "VABSDIFF4", "0101 0000 0--- ----")
|
||||
INST(VADD, "VADD", "0010 00-- ---- ----")
|
||||
INST(VMAD, "VMAD", "0101 1111 ---- ----")
|
||||
INST(VMNMX, "VMNMX", "0011 101- ---- ----")
|
||||
INST(VOTE, "VOTE", "0101 0000 1101 1---")
|
||||
INST(VOTE_vtg, "VOTE (vtg)", "0101 0000 1110 0---")
|
||||
INST(VSET, "VSET", "0100 000- ---- ----")
|
||||
INST(VSETP, "VSETP", "0101 0000 1111 0---")
|
||||
INST(VSHL, "VSHL", "0101 0111 ---- ----")
|
||||
INST(VSHR, "VSHR", "0101 0110 ---- ----")
|
||||
INST(XMAD_reg, "XMAD (reg)", "0101 1011 00-- ----")
|
||||
INST(XMAD_rc, "XMAD (rc)", "0101 0001 0--- ----")
|
||||
INST(XMAD_cr, "XMAD (cr)", "0100 111- ---- ----")
|
||||
INST(XMAD_imm, "XMAD (imm)", "0011 011- 00-- ----")
|
||||
|
||||
// Removed due to its weird formatting making fast tables larger
|
||||
// INST(CCTLT, "CCTLT", "1110 1011 1111 0--0")
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/maxwell/opcode.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
namespace {
|
||||
constexpr std::array NAME_TABLE{
|
||||
#define INST(name, cute, encode) #cute,
|
||||
#include "maxwell.inc"
|
||||
#undef INST
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
const char* NameOf(Opcode opcode) {
|
||||
if (static_cast<size_t>(opcode) >= NAME_TABLE.size()) {
|
||||
throw InvalidArgument("Invalid opcode with raw value {}", static_cast<int>(opcode));
|
||||
}
|
||||
return NAME_TABLE[static_cast<size_t>(opcode)];
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
enum class Opcode {
|
||||
#define INST(name, cute, encode) name,
|
||||
#include "maxwell.inc"
|
||||
#undef INST
|
||||
};
|
||||
|
||||
const char* NameOf(Opcode opcode);
|
||||
|
||||
} // namespace Shader::Maxwell
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Shader::Maxwell::Opcode> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const Shader::Maxwell::Opcode& opcode, FormatContext& ctx) {
|
||||
return format_to(ctx.out(), "{}", NameOf(opcode));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "shader_recompiler/frontend/maxwell/program.h"
|
||||
#include "shader_recompiler/frontend/maxwell/termination_code.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/translate.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
Program::Function::~Function() {
|
||||
std::ranges::for_each(blocks, &std::destroy_at<IR::Block>);
|
||||
}
|
||||
|
||||
Program::Program(Environment& env, const Flow::CFG& cfg) {
|
||||
std::vector<IR::Block*> block_map;
|
||||
functions.reserve(cfg.Functions().size());
|
||||
|
||||
for (const Flow::Function& cfg_function : cfg.Functions()) {
|
||||
Function& function{functions.emplace_back()};
|
||||
|
||||
const size_t num_blocks{cfg_function.blocks.size()};
|
||||
IR::Block* block_memory{block_alloc_pool.allocate(num_blocks)};
|
||||
function.blocks.reserve(num_blocks);
|
||||
|
||||
block_map.resize(cfg_function.blocks_data.size());
|
||||
|
||||
// Visit the instructions of all blocks
|
||||
for (const Flow::BlockId block_id : cfg_function.blocks) {
|
||||
const Flow::Block& flow_block{cfg_function.blocks_data[block_id]};
|
||||
|
||||
IR::Block* const block{std::construct_at(block_memory, Translate(env, flow_block))};
|
||||
++block_memory;
|
||||
function.blocks.push_back(block);
|
||||
block_map[flow_block.id] = block;
|
||||
}
|
||||
// Now that all blocks are defined, emit the termination instructions
|
||||
for (const Flow::BlockId block_id : cfg_function.blocks) {
|
||||
const Flow::Block& flow_block{cfg_function.blocks_data[block_id]};
|
||||
EmitTerminationCode(flow_block, block_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string DumpProgram(const Program& program) {
|
||||
size_t index{0};
|
||||
std::map<const IR::Inst*, size_t> inst_to_index;
|
||||
std::map<const IR::Block*, size_t> block_to_index;
|
||||
|
||||
for (const Program::Function& function : program.functions) {
|
||||
for (const IR::Block* const block : function.blocks) {
|
||||
block_to_index.emplace(block, index);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
std::string ret;
|
||||
for (const Program::Function& function : program.functions) {
|
||||
ret += fmt::format("Function\n");
|
||||
for (const IR::Block* const block : function.blocks) {
|
||||
ret += IR::DumpBlock(*block, block_to_index, inst_to_index, index) + '\n';
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/pool/pool_alloc.hpp>
|
||||
|
||||
#include "shader_recompiler/environment.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
class Program {
|
||||
friend std::string DumpProgram(const Program& program);
|
||||
|
||||
public:
|
||||
explicit Program(Environment& env, const Flow::CFG& cfg);
|
||||
|
||||
private:
|
||||
struct Function {
|
||||
~Function();
|
||||
|
||||
std::vector<IR::Block*> blocks;
|
||||
};
|
||||
|
||||
boost::pool_allocator<IR::Block, boost::default_user_allocator_new_delete,
|
||||
boost::details::pool::null_mutex>
|
||||
block_alloc_pool;
|
||||
std::vector<Function> functions;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::string DumpProgram(const Program& program);
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/ir_emitter.h"
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
#include "shader_recompiler/frontend/maxwell/termination_code.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
static void EmitExit(IR::IREmitter& ir) {
|
||||
ir.Exit();
|
||||
}
|
||||
|
||||
static IR::U1 GetFlowTest(IR::FlowTest flow_test, IR::IREmitter& ir) {
|
||||
switch (flow_test) {
|
||||
case IR::FlowTest::T:
|
||||
return ir.Imm1(true);
|
||||
case IR::FlowTest::F:
|
||||
return ir.Imm1(false);
|
||||
case IR::FlowTest::NE:
|
||||
// FIXME: Verify this
|
||||
return ir.LogicalNot(ir.GetZFlag());
|
||||
case IR::FlowTest::NaN:
|
||||
// FIXME: Verify this
|
||||
return ir.LogicalAnd(ir.GetSFlag(), ir.GetZFlag());
|
||||
default:
|
||||
throw NotImplementedException("Flow test {}", flow_test);
|
||||
}
|
||||
}
|
||||
|
||||
static IR::U1 GetCond(IR::Condition cond, IR::IREmitter& ir) {
|
||||
const IR::FlowTest flow_test{cond.FlowTest()};
|
||||
const auto [pred, pred_negated]{cond.Pred()};
|
||||
if (pred == IR::Pred::PT && !pred_negated) {
|
||||
return GetFlowTest(flow_test, ir);
|
||||
}
|
||||
if (flow_test == IR::FlowTest::T) {
|
||||
return ir.GetPred(pred, pred_negated);
|
||||
}
|
||||
return ir.LogicalAnd(ir.GetPred(pred, pred_negated), GetFlowTest(flow_test, ir));
|
||||
}
|
||||
|
||||
static void EmitBranch(const Flow::Block& flow_block, std::span<IR::Block* const> block_map,
|
||||
IR::IREmitter& ir) {
|
||||
if (flow_block.cond == true) {
|
||||
return ir.Branch(block_map[flow_block.branch_true]);
|
||||
}
|
||||
if (flow_block.cond == false) {
|
||||
return ir.Branch(block_map[flow_block.branch_false]);
|
||||
}
|
||||
return ir.BranchConditional(GetCond(flow_block.cond, ir), block_map[flow_block.branch_true],
|
||||
block_map[flow_block.branch_false]);
|
||||
}
|
||||
|
||||
void EmitTerminationCode(const Flow::Block& flow_block, std::span<IR::Block* const> block_map) {
|
||||
IR::Block* const block{block_map[flow_block.id]};
|
||||
IR::IREmitter ir(*block);
|
||||
switch (flow_block.end_class) {
|
||||
case Flow::EndClass::Branch:
|
||||
EmitBranch(flow_block, block_map, ir);
|
||||
break;
|
||||
case Flow::EndClass::Exit:
|
||||
EmitExit(ir);
|
||||
break;
|
||||
case Flow::EndClass::Return:
|
||||
ir.Return();
|
||||
break;
|
||||
case Flow::EndClass::Unreachable:
|
||||
ir.Unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
void EmitTerminationCode(const Flow::Block& flow_block, std::span<IR::Block* const> block_map);
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
void TranslatorVisitor::EXIT(u64) {
|
||||
ir.Exit();
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,133 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/maxwell/opcode.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
namespace {
|
||||
enum class DestFormat : u64 {
|
||||
Invalid,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
};
|
||||
enum class SrcFormat : u64 {
|
||||
Invalid,
|
||||
F16,
|
||||
F32,
|
||||
F64,
|
||||
};
|
||||
enum class Rounding : u64 {
|
||||
Round,
|
||||
Floor,
|
||||
Ceil,
|
||||
Trunc,
|
||||
};
|
||||
|
||||
union F2I {
|
||||
u64 raw;
|
||||
BitField<0, 8, IR::Reg> dest_reg;
|
||||
BitField<8, 2, DestFormat> dest_format;
|
||||
BitField<10, 2, SrcFormat> src_format;
|
||||
BitField<12, 1, u64> is_signed;
|
||||
BitField<39, 1, Rounding> rounding;
|
||||
BitField<49, 1, u64> half;
|
||||
BitField<44, 1, u64> ftz;
|
||||
BitField<45, 1, u64> abs;
|
||||
BitField<47, 1, u64> cc;
|
||||
BitField<49, 1, u64> neg;
|
||||
};
|
||||
|
||||
size_t BitSize(DestFormat dest_format) {
|
||||
switch (dest_format) {
|
||||
case DestFormat::I16:
|
||||
return 16;
|
||||
case DestFormat::I32:
|
||||
return 32;
|
||||
case DestFormat::I64:
|
||||
return 64;
|
||||
default:
|
||||
throw NotImplementedException("Invalid destination format {}", dest_format);
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateF2I(TranslatorVisitor& v, u64 insn, const IR::U16U32U64& op_a) {
|
||||
// F2I is used to convert from a floating point value to an integer
|
||||
const F2I f2i{insn};
|
||||
|
||||
const IR::U16U32U64 float_value{v.ir.FPAbsNeg(op_a, f2i.abs != 0, f2i.neg != 0)};
|
||||
const IR::U16U32U64 rounded_value{[&] {
|
||||
switch (f2i.rounding) {
|
||||
case Rounding::Round:
|
||||
return v.ir.FPRoundEven(float_value);
|
||||
case Rounding::Floor:
|
||||
return v.ir.FPFloor(float_value);
|
||||
case Rounding::Ceil:
|
||||
return v.ir.FPCeil(float_value);
|
||||
case Rounding::Trunc:
|
||||
return v.ir.FPTrunc(float_value);
|
||||
default:
|
||||
throw NotImplementedException("Invalid F2I rounding {}", f2i.rounding.Value());
|
||||
}
|
||||
}()};
|
||||
|
||||
// TODO: Handle out of bounds conversions.
|
||||
// For example converting F32 65537.0 to U16, the expected value is 0xffff,
|
||||
|
||||
const bool is_signed{f2i.is_signed != 0};
|
||||
const size_t bitsize{BitSize(f2i.dest_format)};
|
||||
const IR::U16U32U64 result{v.ir.ConvertFToI(bitsize, is_signed, rounded_value)};
|
||||
|
||||
v.X(f2i.dest_reg, result);
|
||||
|
||||
if (f2i.cc != 0) {
|
||||
v.SetZFlag(v.ir.GetZeroFromOp(result));
|
||||
if (is_signed) {
|
||||
v.SetSFlag(v.ir.GetSignFromOp(result));
|
||||
} else {
|
||||
v.ResetSFlag();
|
||||
}
|
||||
v.ResetCFlag();
|
||||
|
||||
// TODO: Investigate if out of bound conversions sets the overflow flag
|
||||
v.ResetOFlag();
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void TranslatorVisitor::F2I_reg(u64 insn) {
|
||||
union {
|
||||
F2I base;
|
||||
BitField<20, 8, IR::Reg> src_reg;
|
||||
} const f2i{insn};
|
||||
|
||||
const IR::U16U32U64 op_a{[&]() -> IR::U16U32U64 {
|
||||
switch (f2i.base.src_format) {
|
||||
case SrcFormat::F16:
|
||||
return ir.CompositeExtract(ir.UnpackFloat2x16(X(f2i.src_reg)), f2i.base.half);
|
||||
case SrcFormat::F32:
|
||||
return X(f2i.src_reg);
|
||||
case SrcFormat::F64:
|
||||
return ir.PackDouble2x32(ir.CompositeConstruct(X(f2i.src_reg), X(f2i.src_reg + 1)));
|
||||
default:
|
||||
throw NotImplementedException("Invalid F2I source format {}",
|
||||
f2i.base.src_format.Value());
|
||||
}
|
||||
}()};
|
||||
|
||||
TranslateF2I(*this, insn, op_a);
|
||||
}
|
||||
|
||||
void TranslatorVisitor::F2I_cbuf(u64) {
|
||||
throw NotImplementedException("{}", Opcode::F2I_cbuf);
|
||||
}
|
||||
|
||||
void TranslatorVisitor::F2I_imm(u64) {
|
||||
throw NotImplementedException("{}", Opcode::F2I_imm);
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/maxwell/opcode.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
namespace {
|
||||
enum class Operation {
|
||||
Cos = 0,
|
||||
Sin = 1,
|
||||
Ex2 = 2, // Base 2 exponent
|
||||
Lg2 = 3, // Base 2 logarithm
|
||||
Rcp = 4, // Reciprocal
|
||||
Rsq = 5, // Reciprocal square root
|
||||
Rcp64H = 6, // 64-bit reciprocal
|
||||
Rsq64H = 7, // 64-bit reciprocal square root
|
||||
Sqrt = 8,
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
void TranslatorVisitor::MUFU(u64 insn) {
|
||||
// MUFU is used to implement a bunch of special functions. See Operation.
|
||||
union {
|
||||
u64 raw;
|
||||
BitField<0, 8, IR::Reg> dest_reg;
|
||||
BitField<8, 8, IR::Reg> src_reg;
|
||||
BitField<20, 4, Operation> operation;
|
||||
BitField<46, 1, u64> abs;
|
||||
BitField<48, 1, u64> neg;
|
||||
BitField<50, 1, u64> sat;
|
||||
} const mufu{insn};
|
||||
|
||||
const IR::U32 op_a{ir.FPAbsNeg(X(mufu.src_reg), mufu.abs != 0, mufu.neg != 0)};
|
||||
IR::U32 value{[&]() -> IR::U32 {
|
||||
switch (mufu.operation) {
|
||||
case Operation::Cos:
|
||||
return ir.FPCosNotReduced(op_a);
|
||||
case Operation::Sin:
|
||||
return ir.FPSinNotReduced(op_a);
|
||||
case Operation::Ex2:
|
||||
return ir.FPExp2NotReduced(op_a);
|
||||
case Operation::Lg2:
|
||||
return ir.FPLog2(op_a);
|
||||
case Operation::Rcp:
|
||||
return ir.FPRecip(op_a);
|
||||
case Operation::Rsq:
|
||||
return ir.FPRecipSqrt(op_a);
|
||||
case Operation::Rcp64H:
|
||||
throw NotImplementedException("MUFU.RCP64H");
|
||||
case Operation::Rsq64H:
|
||||
throw NotImplementedException("MUFU.RSQ64H");
|
||||
case Operation::Sqrt:
|
||||
return ir.FPSqrt(op_a);
|
||||
default:
|
||||
throw NotImplementedException("Invalid MUFU operation {}", mufu.operation.Value());
|
||||
}
|
||||
}()};
|
||||
|
||||
if (mufu.sat) {
|
||||
value = ir.FPSaturate(value);
|
||||
}
|
||||
|
||||
X(mufu.dest_reg, value);
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "shader_recompiler/frontend/ir/ir_emitter.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
IR::U32 TranslatorVisitor::X(IR::Reg reg) {
|
||||
return ir.GetReg(reg);
|
||||
}
|
||||
|
||||
void TranslatorVisitor::X(IR::Reg dest_reg, const IR::U32& value) {
|
||||
ir.SetReg(dest_reg, value);
|
||||
}
|
||||
|
||||
IR::U32 TranslatorVisitor::GetCbuf(u64 insn) {
|
||||
union {
|
||||
u64 raw;
|
||||
BitField<20, 14, s64> offset;
|
||||
BitField<34, 5, u64> binding;
|
||||
} const cbuf{insn};
|
||||
if (cbuf.binding >= 18) {
|
||||
throw NotImplementedException("Out of bounds constant buffer binding {}", cbuf.binding);
|
||||
}
|
||||
if (cbuf.offset >= 0x10'000 || cbuf.offset < 0) {
|
||||
throw NotImplementedException("Out of bounds constant buffer offset {}", cbuf.offset);
|
||||
}
|
||||
const IR::U32 binding{ir.Imm32(static_cast<u32>(cbuf.binding))};
|
||||
const IR::U32 byte_offset{ir.Imm32(static_cast<u32>(cbuf.offset) * 4)};
|
||||
return ir.GetCbuf(binding, byte_offset);
|
||||
}
|
||||
|
||||
IR::U32 TranslatorVisitor::GetImm(u64 insn) {
|
||||
union {
|
||||
u64 raw;
|
||||
BitField<20, 19, u64> value;
|
||||
BitField<56, 1, u64> is_negative;
|
||||
} const imm{insn};
|
||||
const s32 positive_value{static_cast<s32>(imm.value)};
|
||||
const s32 value{imm.is_negative != 0 ? -positive_value : positive_value};
|
||||
return ir.Imm32(value);
|
||||
}
|
||||
|
||||
void TranslatorVisitor::SetZFlag(const IR::U1& value) {
|
||||
ir.SetZFlag(value);
|
||||
}
|
||||
|
||||
void TranslatorVisitor::SetSFlag(const IR::U1& value) {
|
||||
ir.SetSFlag(value);
|
||||
}
|
||||
|
||||
void TranslatorVisitor::SetCFlag(const IR::U1& value) {
|
||||
ir.SetCFlag(value);
|
||||
}
|
||||
|
||||
void TranslatorVisitor::SetOFlag(const IR::U1& value) {
|
||||
ir.SetOFlag(value);
|
||||
}
|
||||
|
||||
void TranslatorVisitor::ResetZero() {
|
||||
SetZFlag(ir.Imm1(false));
|
||||
}
|
||||
|
||||
void TranslatorVisitor::ResetSFlag() {
|
||||
SetSFlag(ir.Imm1(false));
|
||||
}
|
||||
|
||||
void TranslatorVisitor::ResetCFlag() {
|
||||
SetCFlag(ir.Imm1(false));
|
||||
}
|
||||
|
||||
void TranslatorVisitor::ResetOFlag() {
|
||||
SetOFlag(ir.Imm1(false));
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,316 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "shader_recompiler/environment.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/ir_emitter.h"
|
||||
#include "shader_recompiler/frontend/maxwell/instruction.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
class TranslatorVisitor {
|
||||
public:
|
||||
explicit TranslatorVisitor(Environment& env_, IR::Block& block) : env{env_} ,ir(block) {}
|
||||
|
||||
Environment& env;
|
||||
IR::IREmitter ir;
|
||||
|
||||
void AL2P(u64 insn);
|
||||
void ALD(u64 insn);
|
||||
void AST(u64 insn);
|
||||
void ATOM_cas(u64 insn);
|
||||
void ATOM(u64 insn);
|
||||
void ATOMS_cas(u64 insn);
|
||||
void ATOMS(u64 insn);
|
||||
void B2R(u64 insn);
|
||||
void BAR(u64 insn);
|
||||
void BFE_reg(u64 insn);
|
||||
void BFE_cbuf(u64 insn);
|
||||
void BFE_imm(u64 insn);
|
||||
void BFI_reg(u64 insn);
|
||||
void BFI_rc(u64 insn);
|
||||
void BFI_cr(u64 insn);
|
||||
void BFI_imm(u64 insn);
|
||||
void BPT(u64 insn);
|
||||
void BRA(u64 insn);
|
||||
void BRK(u64 insn);
|
||||
void BRX(u64 insn);
|
||||
void CAL(u64 insn);
|
||||
void CCTL(u64 insn);
|
||||
void CCTLL(u64 insn);
|
||||
void CONT(u64 insn);
|
||||
void CS2R(u64 insn);
|
||||
void CSET(u64 insn);
|
||||
void CSETP(u64 insn);
|
||||
void DADD_reg(u64 insn);
|
||||
void DADD_cbuf(u64 insn);
|
||||
void DADD_imm(u64 insn);
|
||||
void DEPBAR(u64 insn);
|
||||
void DFMA_reg(u64 insn);
|
||||
void DFMA_rc(u64 insn);
|
||||
void DFMA_cr(u64 insn);
|
||||
void DFMA_imm(u64 insn);
|
||||
void DMNMX_reg(u64 insn);
|
||||
void DMNMX_cbuf(u64 insn);
|
||||
void DMNMX_imm(u64 insn);
|
||||
void DMUL_reg(u64 insn);
|
||||
void DMUL_cbuf(u64 insn);
|
||||
void DMUL_imm(u64 insn);
|
||||
void DSET_reg(u64 insn);
|
||||
void DSET_cbuf(u64 insn);
|
||||
void DSET_imm(u64 insn);
|
||||
void DSETP_reg(u64 insn);
|
||||
void DSETP_cbuf(u64 insn);
|
||||
void DSETP_imm(u64 insn);
|
||||
void EXIT(u64 insn);
|
||||
void F2F_reg(u64 insn);
|
||||
void F2F_cbuf(u64 insn);
|
||||
void F2F_imm(u64 insn);
|
||||
void F2I_reg(u64 insn);
|
||||
void F2I_cbuf(u64 insn);
|
||||
void F2I_imm(u64 insn);
|
||||
void FADD_reg(u64 insn);
|
||||
void FADD_cbuf(u64 insn);
|
||||
void FADD_imm(u64 insn);
|
||||
void FADD32I(u64 insn);
|
||||
void FCHK_reg(u64 insn);
|
||||
void FCHK_cbuf(u64 insn);
|
||||
void FCHK_imm(u64 insn);
|
||||
void FCMP_reg(u64 insn);
|
||||
void FCMP_rc(u64 insn);
|
||||
void FCMP_cr(u64 insn);
|
||||
void FCMP_imm(u64 insn);
|
||||
void FFMA_reg(u64 insn);
|
||||
void FFMA_rc(u64 insn);
|
||||
void FFMA_cr(u64 insn);
|
||||
void FFMA_imm(u64 insn);
|
||||
void FFMA32I(u64 insn);
|
||||
void FLO_reg(u64 insn);
|
||||
void FLO_cbuf(u64 insn);
|
||||
void FLO_imm(u64 insn);
|
||||
void FMNMX_reg(u64 insn);
|
||||
void FMNMX_cbuf(u64 insn);
|
||||
void FMNMX_imm(u64 insn);
|
||||
void FMUL_reg(u64 insn);
|
||||
void FMUL_cbuf(u64 insn);
|
||||
void FMUL_imm(u64 insn);
|
||||
void FMUL32I(u64 insn);
|
||||
void FSET_reg(u64 insn);
|
||||
void FSET_cbuf(u64 insn);
|
||||
void FSET_imm(u64 insn);
|
||||
void FSETP_reg(u64 insn);
|
||||
void FSETP_cbuf(u64 insn);
|
||||
void FSETP_imm(u64 insn);
|
||||
void FSWZADD(u64 insn);
|
||||
void GETCRSPTR(u64 insn);
|
||||
void GETLMEMBASE(u64 insn);
|
||||
void HADD2_reg(u64 insn);
|
||||
void HADD2_cbuf(u64 insn);
|
||||
void HADD2_imm(u64 insn);
|
||||
void HADD2_32I(u64 insn);
|
||||
void HFMA2_reg(u64 insn);
|
||||
void HFMA2_rc(u64 insn);
|
||||
void HFMA2_cr(u64 insn);
|
||||
void HFMA2_imm(u64 insn);
|
||||
void HFMA2_32I(u64 insn);
|
||||
void HMUL2_reg(u64 insn);
|
||||
void HMUL2_cbuf(u64 insn);
|
||||
void HMUL2_imm(u64 insn);
|
||||
void HMUL2_32I(u64 insn);
|
||||
void HSET2_reg(u64 insn);
|
||||
void HSET2_cbuf(u64 insn);
|
||||
void HSET2_imm(u64 insn);
|
||||
void HSETP2_reg(u64 insn);
|
||||
void HSETP2_cbuf(u64 insn);
|
||||
void HSETP2_imm(u64 insn);
|
||||
void I2F_reg(u64 insn);
|
||||
void I2F_cbuf(u64 insn);
|
||||
void I2F_imm(u64 insn);
|
||||
void I2I_reg(u64 insn);
|
||||
void I2I_cbuf(u64 insn);
|
||||
void I2I_imm(u64 insn);
|
||||
void IADD_reg(u64 insn);
|
||||
void IADD_cbuf(u64 insn);
|
||||
void IADD_imm(u64 insn);
|
||||
void IADD3_reg(u64 insn);
|
||||
void IADD3_cbuf(u64 insn);
|
||||
void IADD3_imm(u64 insn);
|
||||
void IADD32I(u64 insn);
|
||||
void ICMP_reg(u64 insn);
|
||||
void ICMP_rc(u64 insn);
|
||||
void ICMP_cr(u64 insn);
|
||||
void ICMP_imm(u64 insn);
|
||||
void IDE(u64 insn);
|
||||
void IDP_reg(u64 insn);
|
||||
void IDP_imm(u64 insn);
|
||||
void IMAD_reg(u64 insn);
|
||||
void IMAD_rc(u64 insn);
|
||||
void IMAD_cr(u64 insn);
|
||||
void IMAD_imm(u64 insn);
|
||||
void IMAD32I(u64 insn);
|
||||
void IMADSP_reg(u64 insn);
|
||||
void IMADSP_rc(u64 insn);
|
||||
void IMADSP_cr(u64 insn);
|
||||
void IMADSP_imm(u64 insn);
|
||||
void IMNMX_reg(u64 insn);
|
||||
void IMNMX_cbuf(u64 insn);
|
||||
void IMNMX_imm(u64 insn);
|
||||
void IMUL_reg(u64 insn);
|
||||
void IMUL_cbuf(u64 insn);
|
||||
void IMUL_imm(u64 insn);
|
||||
void IMUL32I(u64 insn);
|
||||
void IPA(u64 insn);
|
||||
void ISBERD(u64 insn);
|
||||
void ISCADD_reg(u64 insn);
|
||||
void ISCADD_cbuf(u64 insn);
|
||||
void ISCADD_imm(u64 insn);
|
||||
void ISCADD32I(u64 insn);
|
||||
void ISET_reg(u64 insn);
|
||||
void ISET_cbuf(u64 insn);
|
||||
void ISET_imm(u64 insn);
|
||||
void ISETP_reg(u64 insn);
|
||||
void ISETP_cbuf(u64 insn);
|
||||
void ISETP_imm(u64 insn);
|
||||
void JCAL(u64 insn);
|
||||
void JMP(u64 insn);
|
||||
void JMX(u64 insn);
|
||||
void KIL(u64 insn);
|
||||
void LD(u64 insn);
|
||||
void LDC(u64 insn);
|
||||
void LDG(u64 insn);
|
||||
void LDL(u64 insn);
|
||||
void LDS(u64 insn);
|
||||
void LEA_hi_reg(u64 insn);
|
||||
void LEA_hi_cbuf(u64 insn);
|
||||
void LEA_lo_reg(u64 insn);
|
||||
void LEA_lo_cbuf(u64 insn);
|
||||
void LEA_lo_imm(u64 insn);
|
||||
void LEPC(u64 insn);
|
||||
void LONGJMP(u64 insn);
|
||||
void LOP_reg(u64 insn);
|
||||
void LOP_cbuf(u64 insn);
|
||||
void LOP_imm(u64 insn);
|
||||
void LOP3_reg(u64 insn);
|
||||
void LOP3_cbuf(u64 insn);
|
||||
void LOP3_imm(u64 insn);
|
||||
void LOP32I(u64 insn);
|
||||
void MEMBAR(u64 insn);
|
||||
void MOV_reg(u64 insn);
|
||||
void MOV_cbuf(u64 insn);
|
||||
void MOV_imm(u64 insn);
|
||||
void MOV32I(u64 insn);
|
||||
void MUFU(u64 insn);
|
||||
void NOP(u64 insn);
|
||||
void OUT_reg(u64 insn);
|
||||
void OUT_cbuf(u64 insn);
|
||||
void OUT_imm(u64 insn);
|
||||
void P2R_reg(u64 insn);
|
||||
void P2R_cbuf(u64 insn);
|
||||
void P2R_imm(u64 insn);
|
||||
void PBK(u64 insn);
|
||||
void PCNT(u64 insn);
|
||||
void PEXIT(u64 insn);
|
||||
void PIXLD(u64 insn);
|
||||
void PLONGJMP(u64 insn);
|
||||
void POPC_reg(u64 insn);
|
||||
void POPC_cbuf(u64 insn);
|
||||
void POPC_imm(u64 insn);
|
||||
void PRET(u64 insn);
|
||||
void PRMT_reg(u64 insn);
|
||||
void PRMT_rc(u64 insn);
|
||||
void PRMT_cr(u64 insn);
|
||||
void PRMT_imm(u64 insn);
|
||||
void PSET(u64 insn);
|
||||
void PSETP(u64 insn);
|
||||
void R2B(u64 insn);
|
||||
void R2P_reg(u64 insn);
|
||||
void R2P_cbuf(u64 insn);
|
||||
void R2P_imm(u64 insn);
|
||||
void RAM(u64 insn);
|
||||
void RED(u64 insn);
|
||||
void RET(u64 insn);
|
||||
void RRO_reg(u64 insn);
|
||||
void RRO_cbuf(u64 insn);
|
||||
void RRO_imm(u64 insn);
|
||||
void RTT(u64 insn);
|
||||
void S2R(u64 insn);
|
||||
void SAM(u64 insn);
|
||||
void SEL_reg(u64 insn);
|
||||
void SEL_cbuf(u64 insn);
|
||||
void SEL_imm(u64 insn);
|
||||
void SETCRSPTR(u64 insn);
|
||||
void SETLMEMBASE(u64 insn);
|
||||
void SHF_l_reg(u64 insn);
|
||||
void SHF_l_imm(u64 insn);
|
||||
void SHF_r_reg(u64 insn);
|
||||
void SHF_r_imm(u64 insn);
|
||||
void SHFL(u64 insn);
|
||||
void SHL_reg(u64 insn);
|
||||
void SHL_cbuf(u64 insn);
|
||||
void SHL_imm(u64 insn);
|
||||
void SHR_reg(u64 insn);
|
||||
void SHR_cbuf(u64 insn);
|
||||
void SHR_imm(u64 insn);
|
||||
void SSY(u64 insn);
|
||||
void ST(u64 insn);
|
||||
void STG(u64 insn);
|
||||
void STL(u64 insn);
|
||||
void STP(u64 insn);
|
||||
void STS(u64 insn);
|
||||
void SUATOM_cas(u64 insn);
|
||||
void SULD(u64 insn);
|
||||
void SURED(u64 insn);
|
||||
void SUST(u64 insn);
|
||||
void SYNC(u64 insn);
|
||||
void TEX(u64 insn);
|
||||
void TEX_b(u64 insn);
|
||||
void TEXS(u64 insn);
|
||||
void TLD(u64 insn);
|
||||
void TLD_b(u64 insn);
|
||||
void TLD4(u64 insn);
|
||||
void TLD4_b(u64 insn);
|
||||
void TLD4S(u64 insn);
|
||||
void TLDS(u64 insn);
|
||||
void TMML(u64 insn);
|
||||
void TMML_b(u64 insn);
|
||||
void TXA(u64 insn);
|
||||
void TXD(u64 insn);
|
||||
void TXD_b(u64 insn);
|
||||
void TXQ(u64 insn);
|
||||
void TXQ_b(u64 insn);
|
||||
void VABSDIFF(u64 insn);
|
||||
void VABSDIFF4(u64 insn);
|
||||
void VADD(u64 insn);
|
||||
void VMAD(u64 insn);
|
||||
void VMNMX(u64 insn);
|
||||
void VOTE(u64 insn);
|
||||
void VOTE_vtg(u64 insn);
|
||||
void VSET(u64 insn);
|
||||
void VSETP(u64 insn);
|
||||
void VSHL(u64 insn);
|
||||
void VSHR(u64 insn);
|
||||
void XMAD_reg(u64 insn);
|
||||
void XMAD_rc(u64 insn);
|
||||
void XMAD_cr(u64 insn);
|
||||
void XMAD_imm(u64 insn);
|
||||
|
||||
[[nodiscard]] IR::U32 X(IR::Reg reg);
|
||||
void X(IR::Reg dest_reg, const IR::U32& value);
|
||||
|
||||
[[nodiscard]] IR::U32 GetCbuf(u64 insn);
|
||||
|
||||
[[nodiscard]] IR::U32 GetImm(u64 insn);
|
||||
|
||||
void SetZFlag(const IR::U1& value);
|
||||
void SetSFlag(const IR::U1& value);
|
||||
void SetCFlag(const IR::U1& value);
|
||||
void SetOFlag(const IR::U1& value);
|
||||
|
||||
void ResetZero();
|
||||
void ResetSFlag();
|
||||
void ResetCFlag();
|
||||
void ResetOFlag();
|
||||
};
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/maxwell/opcode.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
namespace {
|
||||
enum class InterpolationMode : u64 {
|
||||
Pass = 0,
|
||||
Multiply = 1,
|
||||
Constant = 2,
|
||||
Sc = 3,
|
||||
};
|
||||
|
||||
enum class SampleMode : u64 {
|
||||
Default = 0,
|
||||
Centroid = 1,
|
||||
Offset = 2,
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
void TranslatorVisitor::IPA(u64 insn) {
|
||||
// IPA is the instruction used to read varyings from a fragment shader.
|
||||
// gl_FragCoord is mapped to the gl_Position attribute.
|
||||
// It yields unknown results when used outside of the fragment shader stage.
|
||||
union {
|
||||
u64 raw;
|
||||
BitField<0, 8, IR::Reg> dest_reg;
|
||||
BitField<8, 8, IR::Reg> index_reg;
|
||||
BitField<20, 8, IR::Reg> multiplier;
|
||||
BitField<30, 8, IR::Attribute> attribute;
|
||||
BitField<38, 1, u64> idx;
|
||||
BitField<51, 1, u64> sat;
|
||||
BitField<52, 2, SampleMode> sample_mode;
|
||||
BitField<54, 2, InterpolationMode> interpolation_mode;
|
||||
} const ipa{insn};
|
||||
|
||||
// Indexed IPAs are used for indexed varyings.
|
||||
// For example:
|
||||
//
|
||||
// in vec4 colors[4];
|
||||
// uniform int idx;
|
||||
// void main() {
|
||||
// gl_FragColor = colors[idx];
|
||||
// }
|
||||
const bool is_indexed{ipa.idx != 0 && ipa.index_reg != IR::Reg::RZ};
|
||||
if (is_indexed) {
|
||||
throw NotImplementedException("IPA.IDX");
|
||||
}
|
||||
|
||||
const IR::Attribute attribute{ipa.attribute};
|
||||
IR::U32 value{ir.GetAttribute(attribute)};
|
||||
if (IR::IsGeneric(attribute)) {
|
||||
// const bool is_perspective{UnimplementedReadHeader(GenericAttributeIndex(attribute))};
|
||||
const bool is_perspective{false};
|
||||
if (is_perspective) {
|
||||
const IR::U32 rcp_position_w{ir.FPRecip(ir.GetAttribute(IR::Attribute::PositionW))};
|
||||
value = ir.FPMul(value, rcp_position_w);
|
||||
}
|
||||
}
|
||||
|
||||
switch (ipa.interpolation_mode) {
|
||||
case InterpolationMode::Pass:
|
||||
break;
|
||||
case InterpolationMode::Multiply:
|
||||
value = ir.FPMul(value, ir.GetReg(ipa.multiplier));
|
||||
break;
|
||||
case InterpolationMode::Constant:
|
||||
throw NotImplementedException("IPA.CONSTANT");
|
||||
case InterpolationMode::Sc:
|
||||
throw NotImplementedException("IPA.SC");
|
||||
}
|
||||
|
||||
// Saturated IPAs are generally generated out of clamped varyings.
|
||||
// For example: clamp(some_varying, 0.0, 1.0)
|
||||
const bool is_saturated{ipa.sat != 0};
|
||||
if (is_saturated) {
|
||||
if (attribute == IR::Attribute::FrontFace) {
|
||||
throw NotImplementedException("IPA.SAT on FrontFace");
|
||||
}
|
||||
value = ir.FPSaturate(value);
|
||||
}
|
||||
|
||||
ir.SetReg(ipa.dest_reg, value);
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/maxwell/opcode.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
namespace {
|
||||
enum class StoreSize : u64 {
|
||||
U8,
|
||||
S8,
|
||||
U16,
|
||||
S16,
|
||||
B32,
|
||||
B64,
|
||||
B128,
|
||||
};
|
||||
|
||||
// See Table 28 in https://docs.nvidia.com/cuda/parallel-thread-execution/index.html
|
||||
enum class StoreCache : u64 {
|
||||
WB, // Cache write-back all coherent levels
|
||||
CG, // Cache at global level
|
||||
CS, // Cache streaming, likely to be accessed once
|
||||
WT, // Cache write-through (to system memory)
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
void TranslatorVisitor::STG(u64 insn) {
|
||||
// STG stores registers into global memory.
|
||||
union {
|
||||
u64 raw;
|
||||
BitField<0, 8, IR::Reg> data_reg;
|
||||
BitField<8, 8, IR::Reg> addr_reg;
|
||||
BitField<45, 1, u64> e;
|
||||
BitField<46, 2, StoreCache> cache;
|
||||
BitField<48, 3, StoreSize> size;
|
||||
} const stg{insn};
|
||||
|
||||
const IR::U64 address{[&]() -> IR::U64 {
|
||||
if (stg.e == 0) {
|
||||
// STG without .E uses a 32-bit pointer, zero-extend it
|
||||
return ir.ConvertU(64, X(stg.addr_reg));
|
||||
}
|
||||
if (!IR::IsAligned(stg.addr_reg, 2)) {
|
||||
throw NotImplementedException("Unaligned address register");
|
||||
}
|
||||
// Pack two registers to build the 32-bit address
|
||||
return ir.PackUint2x32(ir.CompositeConstruct(X(stg.addr_reg), X(stg.addr_reg + 1)));
|
||||
}()};
|
||||
|
||||
switch (stg.size) {
|
||||
case StoreSize::U8:
|
||||
ir.WriteGlobalU8(address, X(stg.data_reg));
|
||||
break;
|
||||
case StoreSize::S8:
|
||||
ir.WriteGlobalS8(address, X(stg.data_reg));
|
||||
break;
|
||||
case StoreSize::U16:
|
||||
ir.WriteGlobalU16(address, X(stg.data_reg));
|
||||
break;
|
||||
case StoreSize::S16:
|
||||
ir.WriteGlobalS16(address, X(stg.data_reg));
|
||||
break;
|
||||
case StoreSize::B32:
|
||||
ir.WriteGlobal32(address, X(stg.data_reg));
|
||||
break;
|
||||
case StoreSize::B64: {
|
||||
if (!IR::IsAligned(stg.data_reg, 2)) {
|
||||
throw NotImplementedException("Unaligned data registers");
|
||||
}
|
||||
const IR::Value vector{ir.CompositeConstruct(X(stg.data_reg), X(stg.data_reg + 1))};
|
||||
ir.WriteGlobal64(address, vector);
|
||||
break;
|
||||
}
|
||||
case StoreSize::B128:
|
||||
if (!IR::IsAligned(stg.data_reg, 4)) {
|
||||
throw NotImplementedException("Unaligned data registers");
|
||||
}
|
||||
const IR::Value vector{ir.CompositeConstruct(X(stg.data_reg), X(stg.data_reg + 1),
|
||||
X(stg.data_reg + 2), X(stg.data_reg + 3))};
|
||||
ir.WriteGlobal128(address, vector);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/maxwell/opcode.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
namespace {
|
||||
union MOV {
|
||||
u64 raw;
|
||||
BitField<0, 8, IR::Reg> dest_reg;
|
||||
BitField<20, 8, IR::Reg> src_reg;
|
||||
BitField<39, 4, u64> mask;
|
||||
};
|
||||
|
||||
void CheckMask(MOV mov) {
|
||||
if (mov.mask != 0xf) {
|
||||
throw NotImplementedException("Non-full move mask");
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void TranslatorVisitor::MOV_reg(u64 insn) {
|
||||
const MOV mov{insn};
|
||||
CheckMask(mov);
|
||||
X(mov.dest_reg, X(mov.src_reg));
|
||||
}
|
||||
|
||||
void TranslatorVisitor::MOV_cbuf(u64 insn) {
|
||||
const MOV mov{insn};
|
||||
CheckMask(mov);
|
||||
X(mov.dest_reg, GetCbuf(insn));
|
||||
}
|
||||
|
||||
void TranslatorVisitor::MOV_imm(u64 insn) {
|
||||
const MOV mov{insn};
|
||||
CheckMask(mov);
|
||||
X(mov.dest_reg, GetImm(insn));
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "shader_recompiler/environment.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/maxwell/decode.h"
|
||||
#include "shader_recompiler/frontend/maxwell/location.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/translate.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
template <auto visitor_method>
|
||||
static void Invoke(TranslatorVisitor& visitor, Location pc, u64 insn) {
|
||||
using MethodType = decltype(visitor_method);
|
||||
if constexpr (std::is_invocable_r_v<void, MethodType, TranslatorVisitor&, Location, u64>) {
|
||||
(visitor.*visitor_method)(pc, insn);
|
||||
} else if constexpr (std::is_invocable_r_v<void, MethodType, TranslatorVisitor&, u64>) {
|
||||
(visitor.*visitor_method)(insn);
|
||||
} else {
|
||||
(visitor.*visitor_method)();
|
||||
}
|
||||
}
|
||||
|
||||
IR::Block Translate(Environment& env, const Flow::Block& flow_block) {
|
||||
IR::Block block{flow_block.begin.Offset(), flow_block.end.Offset()};
|
||||
TranslatorVisitor visitor{env, block};
|
||||
|
||||
const Location pc_end{flow_block.end};
|
||||
Location pc{flow_block.begin};
|
||||
while (pc != pc_end) {
|
||||
const u64 insn{env.ReadInstruction(pc.Offset())};
|
||||
const Opcode opcode{Decode(insn)};
|
||||
switch (opcode) {
|
||||
#define INST(name, cute, mask) \
|
||||
case Opcode::name: \
|
||||
Invoke<&TranslatorVisitor::name>(visitor, pc, insn); \
|
||||
break;
|
||||
#include "shader_recompiler/frontend/maxwell/maxwell.inc"
|
||||
#undef OPCODE
|
||||
default:
|
||||
throw LogicError("Invalid opcode {}", opcode);
|
||||
}
|
||||
++pc;
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_recompiler/environment.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/maxwell/location.h"
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
[[nodiscard]] IR::Block Translate(Environment& env, const Flow::Block& flow_block);
|
||||
|
||||
} // namespace Shader::Maxwell
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/microinstruction.h"
|
||||
#include "shader_recompiler/ir_opt/passes.h"
|
||||
|
||||
namespace Shader::Optimization {
|
||||
|
||||
void DeadCodeEliminationPass(IR::Block& block) {
|
||||
// We iterate over the instructions in reverse order.
|
||||
// This is because removing an instruction reduces the number of uses for earlier instructions.
|
||||
for (IR::Inst& inst : std::views::reverse(block)) {
|
||||
if (!inst.HasUses() && !inst.MayHaveSideEffects()) {
|
||||
inst.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Shader::Optimization
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/microinstruction.h"
|
||||
#include "shader_recompiler/ir_opt/passes.h"
|
||||
|
||||
namespace Shader::Optimization {
|
||||
namespace {
|
||||
using Iterator = IR::Block::iterator;
|
||||
|
||||
enum class TrackingType {
|
||||
Reg,
|
||||
};
|
||||
|
||||
struct RegisterInfo {
|
||||
IR::Value register_value;
|
||||
TrackingType tracking_type;
|
||||
Iterator last_set_instruction;
|
||||
bool set_instruction_present = false;
|
||||
};
|
||||
|
||||
void DoSet(IR::Block& block, RegisterInfo& info, IR::Value value, Iterator set_inst,
|
||||
TrackingType tracking_type) {
|
||||
if (info.set_instruction_present) {
|
||||
info.last_set_instruction->Invalidate();
|
||||
block.Instructions().erase(info.last_set_instruction);
|
||||
}
|
||||
info.register_value = value;
|
||||
info.tracking_type = tracking_type;
|
||||
info.set_instruction_present = true;
|
||||
info.last_set_instruction = set_inst;
|
||||
}
|
||||
|
||||
RegisterInfo Nothing(Iterator get_inst, TrackingType tracking_type) {
|
||||
RegisterInfo info{};
|
||||
info.register_value = IR::Value{&*get_inst};
|
||||
info.tracking_type = tracking_type;
|
||||
return info;
|
||||
}
|
||||
|
||||
void DoGet(RegisterInfo& info, Iterator get_inst, TrackingType tracking_type) {
|
||||
if (info.register_value.IsEmpty()) {
|
||||
info = Nothing(get_inst, tracking_type);
|
||||
return;
|
||||
}
|
||||
if (info.tracking_type == tracking_type) {
|
||||
get_inst->ReplaceUsesWith(info.register_value);
|
||||
return;
|
||||
}
|
||||
info = Nothing(get_inst, tracking_type);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void GetSetElimination(IR::Block& block) {
|
||||
std::array<RegisterInfo, 255> reg_info;
|
||||
|
||||
for (Iterator inst = block.begin(); inst != block.end(); ++inst) {
|
||||
switch (inst->Opcode()) {
|
||||
case IR::Opcode::GetRegister: {
|
||||
const IR::Reg reg{inst->Arg(0).Reg()};
|
||||
if (reg == IR::Reg::RZ) {
|
||||
break;
|
||||
}
|
||||
const size_t index{static_cast<size_t>(reg)};
|
||||
DoGet(reg_info.at(index), inst, TrackingType::Reg);
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::SetRegister: {
|
||||
const IR::Reg reg{inst->Arg(0).Reg()};
|
||||
if (reg == IR::Reg::RZ) {
|
||||
break;
|
||||
}
|
||||
const size_t index{static_cast<size_t>(reg)};
|
||||
DoSet(block, reg_info.at(index), inst->Arg(1), inst, TrackingType::Reg);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Shader::Optimization
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/microinstruction.h"
|
||||
#include "shader_recompiler/ir_opt/passes.h"
|
||||
|
||||
namespace Shader::Optimization {
|
||||
|
||||
void IdentityRemovalPass(IR::Block& block) {
|
||||
std::vector<IR::Inst*> to_invalidate;
|
||||
|
||||
for (auto inst = block.begin(); inst != block.end();) {
|
||||
const size_t num_args{inst->NumArgs()};
|
||||
for (size_t i = 0; i < num_args; ++i) {
|
||||
IR::Value arg;
|
||||
while ((arg = inst->Arg(i)).IsIdentity()) {
|
||||
inst->SetArg(i, arg.Inst()->Arg(0));
|
||||
}
|
||||
}
|
||||
if (inst->Opcode() == IR::Opcode::Identity || inst->Opcode() == IR::Opcode::Void) {
|
||||
to_invalidate.push_back(&*inst);
|
||||
inst = block.Instructions().erase(inst);
|
||||
} else {
|
||||
++inst;
|
||||
}
|
||||
}
|
||||
|
||||
for (IR::Inst* const inst : to_invalidate) {
|
||||
inst->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Shader::Optimization
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
|
||||
namespace Shader::Optimization {
|
||||
|
||||
void DeadCodeEliminationPass(IR::Block& block);
|
||||
void GetSetElimination(IR::Block& block);
|
||||
void IdentityRemovalPass(IR::Block& block);
|
||||
void VerificationPass(const IR::Block& block);
|
||||
|
||||
} // namespace Shader::Optimization
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/microinstruction.h"
|
||||
#include "shader_recompiler/ir_opt/passes.h"
|
||||
|
||||
namespace Shader::Optimization {
|
||||
|
||||
static void ValidateTypes(const IR::Block& block) {
|
||||
for (const IR::Inst& inst : block) {
|
||||
const size_t num_args{inst.NumArgs()};
|
||||
for (size_t i = 0; i < num_args; ++i) {
|
||||
const IR::Type t1{inst.Arg(i).Type()};
|
||||
const IR::Type t2{IR::ArgTypeOf(inst.Opcode(), i)};
|
||||
if (!IR::AreTypesCompatible(t1, t2)) {
|
||||
throw LogicError("Invalid types in block:\n{}", IR::DumpBlock(block));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ValidateUses(const IR::Block& block) {
|
||||
std::map<IR::Inst*, int> actual_uses;
|
||||
for (const IR::Inst& inst : block) {
|
||||
const size_t num_args{inst.NumArgs()};
|
||||
for (size_t i = 0; i < num_args; ++i) {
|
||||
const IR::Value arg{inst.Arg(i)};
|
||||
if (!arg.IsImmediate()) {
|
||||
++actual_uses[arg.Inst()];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto [inst, uses] : actual_uses) {
|
||||
if (inst->UseCount() != uses) {
|
||||
throw LogicError("Invalid uses in block:\n{}", IR::DumpBlock(block));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VerificationPass(const IR::Block& block) {
|
||||
ValidateTypes(block);
|
||||
ValidateUses(block);
|
||||
}
|
||||
|
||||
} // namespace Shader::Optimization
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "shader_recompiler/file_environment.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/ir_emitter.h"
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
#include "shader_recompiler/frontend/maxwell/decode.h"
|
||||
#include "shader_recompiler/frontend/maxwell/location.h"
|
||||
#include "shader_recompiler/frontend/maxwell/program.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/translate.h"
|
||||
|
||||
using namespace Shader;
|
||||
using namespace Shader::Maxwell;
|
||||
|
||||
template <typename Func>
|
||||
static void ForEachFile(const std::filesystem::path& path, Func&& func) {
|
||||
std::filesystem::directory_iterator end;
|
||||
for (std::filesystem::directory_iterator it{path}; it != end; ++it) {
|
||||
if (std::filesystem::is_directory(*it)) {
|
||||
ForEachFile(*it, func);
|
||||
} else {
|
||||
func(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RunDatabase() {
|
||||
std::vector<std::unique_ptr<FileEnvironment>> map;
|
||||
ForEachFile("D:\\Shaders\\Database", [&](const std::filesystem::path& path) {
|
||||
map.emplace_back(std::make_unique<FileEnvironment>(path.string().c_str()));
|
||||
});
|
||||
for (int i = 0; i < 1; ++i) {
|
||||
for (auto& env : map) {
|
||||
// fmt::print(stdout, "Decoding {}\n", path.string());
|
||||
const Location start_address{0};
|
||||
auto cfg{std::make_unique<Flow::CFG>(*env, start_address)};
|
||||
// fmt::print(stdout, "{}\n", cfg.Dot());
|
||||
// IR::Program program{env, cfg};
|
||||
// Optimize(program);
|
||||
// const std::string code{EmitGLASM(program)};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
// RunDatabase();
|
||||
|
||||
FileEnvironment env{"D:\\Shaders\\Database\\test.bin"};
|
||||
auto cfg{std::make_unique<Flow::CFG>(env, 0)};
|
||||
// fmt::print(stdout, "{}\n", cfg->Dot());
|
||||
|
||||
Program program{env, *cfg};
|
||||
fmt::print(stdout, "{}\n", DumpProgram(program));
|
||||
}
|
Reference in New Issue