diff options
| author | 2021-07-25 11:39:04 -0700 | |
|---|---|---|
| committer | 2021-07-25 11:39:04 -0700 | |
| commit | 98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f (patch) | |
| tree | 816faa96c2c4d291825063433331a8ea4b3d08f1 /src/shader_recompiler/frontend/ir/value.h | |
| parent | Merge pull request #6699 from lat9nq/common-threads (diff) | |
| parent | shader: Support out of bound local memory reads and immediate writes (diff) | |
| download | yuzu-98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f.tar.gz yuzu-98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f.tar.xz yuzu-98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f.zip | |
Merge pull request #6585 from ameerj/hades
Shader Decompiler Rewrite
Diffstat (limited to '')
| -rw-r--r-- | src/shader_recompiler/frontend/ir/value.h | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/src/shader_recompiler/frontend/ir/value.h b/src/shader_recompiler/frontend/ir/value.h new file mode 100644 index 000000000..0c6bf684d --- /dev/null +++ b/src/shader_recompiler/frontend/ir/value.h | |||
| @@ -0,0 +1,398 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <cstring> | ||
| 9 | #include <memory> | ||
| 10 | #include <type_traits> | ||
| 11 | #include <utility> | ||
| 12 | #include <vector> | ||
| 13 | |||
| 14 | #include <boost/container/small_vector.hpp> | ||
| 15 | #include <boost/intrusive/list.hpp> | ||
| 16 | |||
| 17 | #include "common/assert.h" | ||
| 18 | #include "common/bit_cast.h" | ||
| 19 | #include "common/common_types.h" | ||
| 20 | #include "shader_recompiler/exception.h" | ||
| 21 | #include "shader_recompiler/frontend/ir/attribute.h" | ||
| 22 | #include "shader_recompiler/frontend/ir/opcodes.h" | ||
| 23 | #include "shader_recompiler/frontend/ir/patch.h" | ||
| 24 | #include "shader_recompiler/frontend/ir/pred.h" | ||
| 25 | #include "shader_recompiler/frontend/ir/reg.h" | ||
| 26 | #include "shader_recompiler/frontend/ir/type.h" | ||
| 27 | #include "shader_recompiler/frontend/ir/value.h" | ||
| 28 | |||
| 29 | namespace Shader::IR { | ||
| 30 | |||
| 31 | class Block; | ||
| 32 | class Inst; | ||
| 33 | |||
| 34 | struct AssociatedInsts; | ||
| 35 | |||
| 36 | class Value { | ||
| 37 | public: | ||
| 38 | Value() noexcept = default; | ||
| 39 | explicit Value(IR::Inst* value) noexcept; | ||
| 40 | explicit Value(IR::Reg value) noexcept; | ||
| 41 | explicit Value(IR::Pred value) noexcept; | ||
| 42 | explicit Value(IR::Attribute value) noexcept; | ||
| 43 | explicit Value(IR::Patch value) noexcept; | ||
| 44 | explicit Value(bool value) noexcept; | ||
| 45 | explicit Value(u8 value) noexcept; | ||
| 46 | explicit Value(u16 value) noexcept; | ||
| 47 | explicit Value(u32 value) noexcept; | ||
| 48 | explicit Value(f32 value) noexcept; | ||
| 49 | explicit Value(u64 value) noexcept; | ||
| 50 | explicit Value(f64 value) noexcept; | ||
| 51 | |||
| 52 | [[nodiscard]] bool IsIdentity() const noexcept; | ||
| 53 | [[nodiscard]] bool IsPhi() const noexcept; | ||
| 54 | [[nodiscard]] bool IsEmpty() const noexcept; | ||
| 55 | [[nodiscard]] bool IsImmediate() const noexcept; | ||
| 56 | [[nodiscard]] IR::Type Type() const noexcept; | ||
| 57 | |||
| 58 | [[nodiscard]] IR::Inst* Inst() const; | ||
| 59 | [[nodiscard]] IR::Inst* InstRecursive() const; | ||
| 60 | [[nodiscard]] IR::Value Resolve() const; | ||
| 61 | [[nodiscard]] IR::Reg Reg() const; | ||
| 62 | [[nodiscard]] IR::Pred Pred() const; | ||
| 63 | [[nodiscard]] IR::Attribute Attribute() const; | ||
| 64 | [[nodiscard]] IR::Patch Patch() const; | ||
| 65 | [[nodiscard]] bool U1() const; | ||
| 66 | [[nodiscard]] u8 U8() const; | ||
| 67 | [[nodiscard]] u16 U16() const; | ||
| 68 | [[nodiscard]] u32 U32() const; | ||
| 69 | [[nodiscard]] f32 F32() const; | ||
| 70 | [[nodiscard]] u64 U64() const; | ||
| 71 | [[nodiscard]] f64 F64() const; | ||
| 72 | |||
| 73 | [[nodiscard]] bool operator==(const Value& other) const; | ||
| 74 | [[nodiscard]] bool operator!=(const Value& other) const; | ||
| 75 | |||
| 76 | private: | ||
| 77 | IR::Type type{}; | ||
| 78 | union { | ||
| 79 | IR::Inst* inst{}; | ||
| 80 | IR::Reg reg; | ||
| 81 | IR::Pred pred; | ||
| 82 | IR::Attribute attribute; | ||
| 83 | IR::Patch patch; | ||
| 84 | bool imm_u1; | ||
| 85 | u8 imm_u8; | ||
| 86 | u16 imm_u16; | ||
| 87 | u32 imm_u32; | ||
| 88 | f32 imm_f32; | ||
| 89 | u64 imm_u64; | ||
| 90 | f64 imm_f64; | ||
| 91 | }; | ||
| 92 | }; | ||
| 93 | static_assert(static_cast<u32>(IR::Type::Void) == 0, "memset relies on IR::Type being zero"); | ||
| 94 | static_assert(std::is_trivially_copyable_v<Value>); | ||
| 95 | |||
| 96 | template <IR::Type type_> | ||
| 97 | class TypedValue : public Value { | ||
| 98 | public: | ||
| 99 | TypedValue() = default; | ||
| 100 | |||
| 101 | template <IR::Type other_type> | ||
| 102 | requires((other_type & type_) != IR::Type::Void) explicit(false) | ||
| 103 | TypedValue(const TypedValue<other_type>& value) | ||
| 104 | : Value(value) {} | ||
| 105 | |||
| 106 | explicit TypedValue(const Value& value) : Value(value) { | ||
| 107 | if ((value.Type() & type_) == IR::Type::Void) { | ||
| 108 | throw InvalidArgument("Incompatible types {} and {}", type_, value.Type()); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | explicit TypedValue(IR::Inst* inst_) : TypedValue(Value(inst_)) {} | ||
| 113 | }; | ||
| 114 | |||
| 115 | class Inst : public boost::intrusive::list_base_hook<> { | ||
| 116 | public: | ||
| 117 | explicit Inst(IR::Opcode op_, u32 flags_) noexcept; | ||
| 118 | ~Inst(); | ||
| 119 | |||
| 120 | Inst& operator=(const Inst&) = delete; | ||
| 121 | Inst(const Inst&) = delete; | ||
| 122 | |||
| 123 | Inst& operator=(Inst&&) = delete; | ||
| 124 | Inst(Inst&&) = delete; | ||
| 125 | |||
| 126 | /// Get the number of uses this instruction has. | ||
| 127 | [[nodiscard]] int UseCount() const noexcept { | ||
| 128 | return use_count; | ||
| 129 | } | ||
| 130 | |||
| 131 | /// Determines whether this instruction has uses or not. | ||
| 132 | [[nodiscard]] bool HasUses() const noexcept { | ||
| 133 | return use_count > 0; | ||
| 134 | } | ||
| 135 | |||
| 136 | /// Get the opcode this microinstruction represents. | ||
| 137 | [[nodiscard]] IR::Opcode GetOpcode() const noexcept { | ||
| 138 | return op; | ||
| 139 | } | ||
| 140 | |||
| 141 | /// Determines if there is a pseudo-operation associated with this instruction. | ||
| 142 | [[nodiscard]] bool HasAssociatedPseudoOperation() const noexcept { | ||
| 143 | return associated_insts != nullptr; | ||
| 144 | } | ||
| 145 | |||
| 146 | /// Determines whether or not this instruction may have side effects. | ||
| 147 | [[nodiscard]] bool MayHaveSideEffects() const noexcept; | ||
| 148 | |||
| 149 | /// Determines whether or not this instruction is a pseudo-instruction. | ||
| 150 | /// Pseudo-instructions depend on their parent instructions for their semantics. | ||
| 151 | [[nodiscard]] bool IsPseudoInstruction() const noexcept; | ||
| 152 | |||
| 153 | /// Determines if all arguments of this instruction are immediates. | ||
| 154 | [[nodiscard]] bool AreAllArgsImmediates() const; | ||
| 155 | |||
| 156 | /// Gets a pseudo-operation associated with this instruction | ||
| 157 | [[nodiscard]] Inst* GetAssociatedPseudoOperation(IR::Opcode opcode); | ||
| 158 | |||
| 159 | /// Get the type this instruction returns. | ||
| 160 | [[nodiscard]] IR::Type Type() const; | ||
| 161 | |||
| 162 | /// Get the number of arguments this instruction has. | ||
| 163 | [[nodiscard]] size_t NumArgs() const { | ||
| 164 | return op == IR::Opcode::Phi ? phi_args.size() : NumArgsOf(op); | ||
| 165 | } | ||
| 166 | |||
| 167 | /// Get the value of a given argument index. | ||
| 168 | [[nodiscard]] Value Arg(size_t index) const noexcept { | ||
| 169 | if (op == IR::Opcode::Phi) { | ||
| 170 | return phi_args[index].second; | ||
| 171 | } else { | ||
| 172 | return args[index]; | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | /// Set the value of a given argument index. | ||
| 177 | void SetArg(size_t index, Value value); | ||
| 178 | |||
| 179 | /// Get a pointer to the block of a phi argument. | ||
| 180 | [[nodiscard]] Block* PhiBlock(size_t index) const; | ||
| 181 | /// Add phi operand to a phi instruction. | ||
| 182 | void AddPhiOperand(Block* predecessor, const Value& value); | ||
| 183 | |||
| 184 | void Invalidate(); | ||
| 185 | void ClearArgs(); | ||
| 186 | |||
| 187 | void ReplaceUsesWith(Value replacement); | ||
| 188 | |||
| 189 | void ReplaceOpcode(IR::Opcode opcode); | ||
| 190 | |||
| 191 | template <typename FlagsType> | ||
| 192 | requires(sizeof(FlagsType) <= sizeof(u32) && std::is_trivially_copyable_v<FlagsType>) | ||
| 193 | [[nodiscard]] FlagsType Flags() const noexcept { | ||
| 194 | FlagsType ret; | ||
| 195 | std::memcpy(reinterpret_cast<char*>(&ret), &flags, sizeof(ret)); | ||
| 196 | return ret; | ||
| 197 | } | ||
| 198 | |||
| 199 | template <typename FlagsType> | ||
| 200 | requires(sizeof(FlagsType) <= sizeof(u32) && std::is_trivially_copyable_v<FlagsType>) | ||
| 201 | [[nodiscard]] void SetFlags(FlagsType value) noexcept { | ||
| 202 | std::memcpy(&flags, &value, sizeof(value)); | ||
| 203 | } | ||
| 204 | |||
| 205 | /// Intrusively store the host definition of this instruction. | ||
| 206 | template <typename DefinitionType> | ||
| 207 | void SetDefinition(DefinitionType def) { | ||
| 208 | definition = Common::BitCast<u32>(def); | ||
| 209 | } | ||
| 210 | |||
| 211 | /// Return the intrusively stored host definition of this instruction. | ||
| 212 | template <typename DefinitionType> | ||
| 213 | [[nodiscard]] DefinitionType Definition() const noexcept { | ||
| 214 | return Common::BitCast<DefinitionType>(definition); | ||
| 215 | } | ||
| 216 | |||
| 217 | /// Destructively remove one reference count from the instruction | ||
| 218 | /// Useful for register allocation | ||
| 219 | void DestructiveRemoveUsage() { | ||
| 220 | --use_count; | ||
| 221 | } | ||
| 222 | |||
| 223 | /// Destructively add usages to the instruction | ||
| 224 | /// Useful for register allocation | ||
| 225 | void DestructiveAddUsage(int count) { | ||
| 226 | use_count += count; | ||
| 227 | } | ||
| 228 | |||
| 229 | private: | ||
| 230 | struct NonTriviallyDummy { | ||
| 231 | NonTriviallyDummy() noexcept {} | ||
| 232 | }; | ||
| 233 | |||
| 234 | void Use(const Value& value); | ||
| 235 | void UndoUse(const Value& value); | ||
| 236 | |||
| 237 | IR::Opcode op{}; | ||
| 238 | int use_count{}; | ||
| 239 | u32 flags{}; | ||
| 240 | u32 definition{}; | ||
| 241 | union { | ||
| 242 | NonTriviallyDummy dummy{}; | ||
| 243 | boost::container::small_vector<std::pair<Block*, Value>, 2> phi_args; | ||
| 244 | std::array<Value, 5> args; | ||
| 245 | }; | ||
| 246 | std::unique_ptr<AssociatedInsts> associated_insts; | ||
| 247 | }; | ||
| 248 | static_assert(sizeof(Inst) <= 128, "Inst size unintentionally increased"); | ||
| 249 | |||
| 250 | struct AssociatedInsts { | ||
| 251 | union { | ||
| 252 | Inst* in_bounds_inst; | ||
| 253 | Inst* sparse_inst; | ||
| 254 | Inst* zero_inst{}; | ||
| 255 | }; | ||
| 256 | Inst* sign_inst{}; | ||
| 257 | Inst* carry_inst{}; | ||
| 258 | Inst* overflow_inst{}; | ||
| 259 | }; | ||
| 260 | |||
| 261 | using U1 = TypedValue<Type::U1>; | ||
| 262 | using U8 = TypedValue<Type::U8>; | ||
| 263 | using U16 = TypedValue<Type::U16>; | ||
| 264 | using U32 = TypedValue<Type::U32>; | ||
| 265 | using U64 = TypedValue<Type::U64>; | ||
| 266 | using F16 = TypedValue<Type::F16>; | ||
| 267 | using F32 = TypedValue<Type::F32>; | ||
| 268 | using F64 = TypedValue<Type::F64>; | ||
| 269 | using U32U64 = TypedValue<Type::U32 | Type::U64>; | ||
| 270 | using F32F64 = TypedValue<Type::F32 | Type::F64>; | ||
| 271 | using U16U32U64 = TypedValue<Type::U16 | Type::U32 | Type::U64>; | ||
| 272 | using F16F32F64 = TypedValue<Type::F16 | Type::F32 | Type::F64>; | ||
| 273 | using UAny = TypedValue<Type::U8 | Type::U16 | Type::U32 | Type::U64>; | ||
| 274 | |||
| 275 | inline bool Value::IsIdentity() const noexcept { | ||
| 276 | return type == Type::Opaque && inst->GetOpcode() == Opcode::Identity; | ||
| 277 | } | ||
| 278 | |||
| 279 | inline bool Value::IsPhi() const noexcept { | ||
| 280 | return type == Type::Opaque && inst->GetOpcode() == Opcode::Phi; | ||
| 281 | } | ||
| 282 | |||
| 283 | inline bool Value::IsEmpty() const noexcept { | ||
| 284 | return type == Type::Void; | ||
| 285 | } | ||
| 286 | |||
| 287 | inline bool Value::IsImmediate() const noexcept { | ||
| 288 | IR::Type current_type{type}; | ||
| 289 | const IR::Inst* current_inst{inst}; | ||
| 290 | while (current_type == Type::Opaque && current_inst->GetOpcode() == Opcode::Identity) { | ||
| 291 | const Value& arg{current_inst->Arg(0)}; | ||
| 292 | current_type = arg.type; | ||
| 293 | current_inst = arg.inst; | ||
| 294 | } | ||
| 295 | return current_type != Type::Opaque; | ||
| 296 | } | ||
| 297 | |||
| 298 | inline IR::Inst* Value::Inst() const { | ||
| 299 | DEBUG_ASSERT(type == Type::Opaque); | ||
| 300 | return inst; | ||
| 301 | } | ||
| 302 | |||
| 303 | inline IR::Inst* Value::InstRecursive() const { | ||
| 304 | DEBUG_ASSERT(type == Type::Opaque); | ||
| 305 | if (IsIdentity()) { | ||
| 306 | return inst->Arg(0).InstRecursive(); | ||
| 307 | } | ||
| 308 | return inst; | ||
| 309 | } | ||
| 310 | |||
| 311 | inline IR::Value Value::Resolve() const { | ||
| 312 | if (IsIdentity()) { | ||
| 313 | return inst->Arg(0).Resolve(); | ||
| 314 | } | ||
| 315 | return *this; | ||
| 316 | } | ||
| 317 | |||
| 318 | inline IR::Reg Value::Reg() const { | ||
| 319 | DEBUG_ASSERT(type == Type::Reg); | ||
| 320 | return reg; | ||
| 321 | } | ||
| 322 | |||
| 323 | inline IR::Pred Value::Pred() const { | ||
| 324 | DEBUG_ASSERT(type == Type::Pred); | ||
| 325 | return pred; | ||
| 326 | } | ||
| 327 | |||
| 328 | inline IR::Attribute Value::Attribute() const { | ||
| 329 | DEBUG_ASSERT(type == Type::Attribute); | ||
| 330 | return attribute; | ||
| 331 | } | ||
| 332 | |||
| 333 | inline IR::Patch Value::Patch() const { | ||
| 334 | DEBUG_ASSERT(type == Type::Patch); | ||
| 335 | return patch; | ||
| 336 | } | ||
| 337 | |||
| 338 | inline bool Value::U1() const { | ||
| 339 | if (IsIdentity()) { | ||
| 340 | return inst->Arg(0).U1(); | ||
| 341 | } | ||
| 342 | DEBUG_ASSERT(type == Type::U1); | ||
| 343 | return imm_u1; | ||
| 344 | } | ||
| 345 | |||
| 346 | inline u8 Value::U8() const { | ||
| 347 | if (IsIdentity()) { | ||
| 348 | return inst->Arg(0).U8(); | ||
| 349 | } | ||
| 350 | DEBUG_ASSERT(type == Type::U8); | ||
| 351 | return imm_u8; | ||
| 352 | } | ||
| 353 | |||
| 354 | inline u16 Value::U16() const { | ||
| 355 | if (IsIdentity()) { | ||
| 356 | return inst->Arg(0).U16(); | ||
| 357 | } | ||
| 358 | DEBUG_ASSERT(type == Type::U16); | ||
| 359 | return imm_u16; | ||
| 360 | } | ||
| 361 | |||
| 362 | inline u32 Value::U32() const { | ||
| 363 | if (IsIdentity()) { | ||
| 364 | return inst->Arg(0).U32(); | ||
| 365 | } | ||
| 366 | DEBUG_ASSERT(type == Type::U32); | ||
| 367 | return imm_u32; | ||
| 368 | } | ||
| 369 | |||
| 370 | inline f32 Value::F32() const { | ||
| 371 | if (IsIdentity()) { | ||
| 372 | return inst->Arg(0).F32(); | ||
| 373 | } | ||
| 374 | DEBUG_ASSERT(type == Type::F32); | ||
| 375 | return imm_f32; | ||
| 376 | } | ||
| 377 | |||
| 378 | inline u64 Value::U64() const { | ||
| 379 | if (IsIdentity()) { | ||
| 380 | return inst->Arg(0).U64(); | ||
| 381 | } | ||
| 382 | DEBUG_ASSERT(type == Type::U64); | ||
| 383 | return imm_u64; | ||
| 384 | } | ||
| 385 | |||
| 386 | inline f64 Value::F64() const { | ||
| 387 | if (IsIdentity()) { | ||
| 388 | return inst->Arg(0).F64(); | ||
| 389 | } | ||
| 390 | DEBUG_ASSERT(type == Type::F64); | ||
| 391 | return imm_f64; | ||
| 392 | } | ||
| 393 | |||
| 394 | [[nodiscard]] inline bool IsPhi(const Inst& inst) { | ||
| 395 | return inst.GetOpcode() == Opcode::Phi; | ||
| 396 | } | ||
| 397 | |||
| 398 | } // namespace Shader::IR | ||