committed by
ameerj
57 changed files with 7061 additions and 0 deletions
-
1src/CMakeLists.txt
-
86src/shader_recompiler/CMakeLists.txt
-
14src/shader_recompiler/environment.h
-
42src/shader_recompiler/exception.h
-
42src/shader_recompiler/file_environment.cpp
-
21src/shader_recompiler/file_environment.h
-
447src/shader_recompiler/frontend/ir/attribute.cpp
-
242src/shader_recompiler/frontend/ir/attribute.h
-
142src/shader_recompiler/frontend/ir/basic_block.cpp
-
134src/shader_recompiler/frontend/ir/basic_block.h
-
31src/shader_recompiler/frontend/ir/condition.cpp
-
60src/shader_recompiler/frontend/ir/condition.h
-
83src/shader_recompiler/frontend/ir/flow_test.cpp
-
61src/shader_recompiler/frontend/ir/flow_test.h
-
533src/shader_recompiler/frontend/ir/ir_emitter.cpp
-
123src/shader_recompiler/frontend/ir/ir_emitter.h
-
189src/shader_recompiler/frontend/ir/microinstruction.cpp
-
82src/shader_recompiler/frontend/ir/microinstruction.h
-
67src/shader_recompiler/frontend/ir/opcode.cpp
-
44src/shader_recompiler/frontend/ir/opcode.h
-
142src/shader_recompiler/frontend/ir/opcode.inc
-
28src/shader_recompiler/frontend/ir/pred.h
-
314src/shader_recompiler/frontend/ir/reg.h
-
36src/shader_recompiler/frontend/ir/type.cpp
-
47src/shader_recompiler/frontend/ir/type.h
-
124src/shader_recompiler/frontend/ir/value.cpp
-
98src/shader_recompiler/frontend/ir/value.h
-
531src/shader_recompiler/frontend/maxwell/control_flow.cpp
-
137src/shader_recompiler/frontend/maxwell/control_flow.h
-
149src/shader_recompiler/frontend/maxwell/decode.cpp
-
14src/shader_recompiler/frontend/maxwell/decode.h
-
62src/shader_recompiler/frontend/maxwell/instruction.h
-
106src/shader_recompiler/frontend/maxwell/location.h
-
285src/shader_recompiler/frontend/maxwell/maxwell.inc
-
26src/shader_recompiler/frontend/maxwell/opcode.cpp
-
30src/shader_recompiler/frontend/maxwell/opcode.h
-
69src/shader_recompiler/frontend/maxwell/program.cpp
-
39src/shader_recompiler/frontend/maxwell/program.h
-
79src/shader_recompiler/frontend/maxwell/termination_code.cpp
-
16src/shader_recompiler/frontend/maxwell/termination_code.h
-
15src/shader_recompiler/frontend/maxwell/translate/impl/exit.cpp
-
133src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp
-
71src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multi_function.cpp
-
79src/shader_recompiler/frontend/maxwell/translate/impl/impl.cpp
-
316src/shader_recompiler/frontend/maxwell/translate/impl/impl.h
-
92src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp
-
90src/shader_recompiler/frontend/maxwell/translate/impl/load_store_memory.cpp
-
1105src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
-
45src/shader_recompiler/frontend/maxwell/translate/impl/register_move.cpp
-
50src/shader_recompiler/frontend/maxwell/translate/translate.cpp
-
16src/shader_recompiler/frontend/maxwell/translate/translate.h
-
23src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp
-
87src/shader_recompiler/ir_opt/get_set_elimination_pass.cpp
-
37src/shader_recompiler/ir_opt/identity_removal_pass.cpp
-
16src/shader_recompiler/ir_opt/passes.h
-
50src/shader_recompiler/ir_opt/verification_pass.cpp
-
60src/shader_recompiler/main.cpp
@ -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
|
|||
1105
src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -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)); |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue