diff --git a/emulation/rocjitsu/lib/python/amdisa/codegen/_generator.py b/emulation/rocjitsu/lib/python/amdisa/codegen/_generator.py index 2018305b99c0..90bf063cbc1b 100644 --- a/emulation/rocjitsu/lib/python/amdisa/codegen/_generator.py +++ b/emulation/rocjitsu/lib/python/amdisa/codegen/_generator.py @@ -2120,6 +2120,51 @@ def _encoding_implicit_uses_impl( 'static_cast(inst_.saddr), 2});' '}' ) + # SDWA dst_unused:PRESERVE and DPP that keeps lanes (partial row/bank + # mask, or bound_ctrl == 0 with an edge-crossing dpp_ctrl) leave the old + # destination in place, so surface the preserved destination as a use. + # This matches what the executor actually restores: the non-VOPC SDWA/DPP + # merge only rewrites old vdst bytes/lanes via write_vgpr, so only a + # VGPR-class destination is preserved. SGPR destinations are fully + # written and never restored -- a VOP3_SDST_ENC carry (v_add_co_ci) and a + # VOP3 compare's SGPR mask alike -- so filter to RegClass::VGPR. The ref + # still comes from the decoded dst operand to get the real width. + # + # SDWA exists only on VOP1/VOP2; DPP additionally exists on VOP3/VOP3P/ + # VOP3_SDST_ENC on gfx11+ (gated -- GCN/CDNA lack the dpp_* fields). + # VOPC/VOPCX stay omitted: their result lands in VCC/EXEC, which liveness + # does not track (see liveness.h). + enc = inst_enc.enc_name.upper() + has_sdwa = enc in ('ENC_VOP1', 'ENC_VOP2') + has_dpp = enc in ('ENC_VOP1', 'ENC_VOP2') or ( + enc in ('ENC_VOP3', 'ENC_VOP3P', 'VOP3_SDST_ENC') + and self._supports_vop_dpp_encoding(enc) + ) + if (has_sdwa or has_dpp) and 'vdst' in enc_field_names: + decls = '' + guards = [] + if has_sdwa: + decls += ( + 'bool sdwa_preserve = sdwa_dst_sel_ != amdgpu::sdwa::DWORD && ' + 'sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; ' + ) + guards.append('sdwa_preserve') + if has_dpp: + decls += ( + 'bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && ' + '(dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || ' + '(dpp_bound_ctrl_ == 0 && ' + 'amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); ' + ) + guards.append('dpp_partial') + return ( + decls + f'if ({" || ".join(guards)}) ' + 'for (int i = 0; i < num_dst_operands(); ++i) ' + 'if (const auto *dst = dst_operand(i)) ' + 'if (auto ref = dst->to_register_ref()) ' + 'if (ref->cls == RegClass::VGPR) ' + 'uses.expand(*ref);' + ) return '' def _enc_field_at_bit(self, enc_name: str, bit_offset: int) -> str | None: diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna1/encodings.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna1/encodings.cpp index 4491989d12e5..22c03d966d4c 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna1/encodings.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna1/encodings.cpp @@ -128,6 +128,20 @@ Vop1::Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop1::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop1::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 255 && inst_.src0 != 249; } @@ -168,6 +182,20 @@ Vop2::Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exe literal_ = reinterpret_cast(inst)[1]; } +void Vop2::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop2::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 255 && inst_.src0 != 249; } diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna1/encodings.h b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna1/encodings.h index 5c7e037fbf1a..94d11639205b 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna1/encodings.h +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna1/encodings.h @@ -475,6 +475,7 @@ class Smem : public IsaInstruction { class Vop1 : public IsaInstruction { public: Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool has_dpp(); @@ -531,6 +532,7 @@ class Vopc : public IsaInstruction { class Vop2 : public IsaInstruction { public: Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool has_dpp(); diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna2/encodings.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna2/encodings.cpp index 986f4f852939..7b4582183c80 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna2/encodings.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna2/encodings.cpp @@ -128,6 +128,20 @@ Vop1::Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop1::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop1::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 255 && inst_.src0 != 249; } @@ -168,6 +182,20 @@ Vop2::Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exe literal_ = reinterpret_cast(inst)[1]; } +void Vop2::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop2::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 255 && inst_.src0 != 249; } diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna2/encodings.h b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna2/encodings.h index 26f65d427d84..4ef380e9b919 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna2/encodings.h +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna2/encodings.h @@ -459,6 +459,7 @@ class Smem : public IsaInstruction { class Vop1 : public IsaInstruction { public: Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool has_dpp(); @@ -515,6 +516,7 @@ class Vopc : public IsaInstruction { class Vop2 : public IsaInstruction { public: Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool has_dpp(); diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna3/encodings.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna3/encodings.cpp index 31799f6746b9..0c607640c908 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna3/encodings.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna3/encodings.cpp @@ -128,6 +128,20 @@ Vop1::Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop1::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop1::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 255 && inst_.src0 != 249; } @@ -168,6 +182,20 @@ Vop2::Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exe literal_ = reinterpret_cast(inst)[1]; } +void Vop2::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop2::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 255 && inst_.src0 != 249; } diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna3/encodings.h b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna3/encodings.h index a05b14a425a3..6e8c1ea1bd07 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna3/encodings.h +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna3/encodings.h @@ -447,6 +447,7 @@ class Smem : public IsaInstruction { class Vop1 : public IsaInstruction { public: Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool has_dpp(); @@ -503,6 +504,7 @@ class Vopc : public IsaInstruction { class Vop2 : public IsaInstruction { public: Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool has_dpp(); diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna4/encodings.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna4/encodings.cpp index 00e22edf1a44..d3f3960881a8 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna4/encodings.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna4/encodings.cpp @@ -122,6 +122,20 @@ Vop1::Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop1::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop1::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 255 && inst_.src0 != 249; } @@ -162,6 +176,20 @@ Vop2::Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exe literal_ = reinterpret_cast(inst)[1]; } +void Vop2::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop2::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 255 && inst_.src0 != 249; } diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna4/encodings.h b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna4/encodings.h index 858f1bed3219..8cdfabf9d78b 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna4/encodings.h +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/cdna4/encodings.h @@ -449,6 +449,7 @@ class Smem : public IsaInstruction { class Vop1 : public IsaInstruction { public: Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool has_dpp(); @@ -505,6 +506,7 @@ class Vopc : public IsaInstruction { class Vop2 : public IsaInstruction { public: Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool has_dpp(); diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/gfx1250/encodings.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/gfx1250/encodings.cpp index 17d9e18c6392..328d12893818 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/gfx1250/encodings.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/gfx1250/encodings.cpp @@ -171,6 +171,20 @@ Vop1::Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop1::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop1::default_encoding() { return (inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255 && inst_.src0 != 254); @@ -223,6 +237,20 @@ Vop2::Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exe literal_ = reinterpret_cast(inst)[1]; } +void Vop2::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop2::default_encoding() { return (inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255 && inst_.src0 != 254); @@ -253,6 +281,18 @@ Vop3::Vop3(std::string_view mnemonic, const Vop3MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop3::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3::has_lit_0() { return (((inst_.src0 == 255) && (inst_.src1 != 255)) && (inst_.src2 != 255)); } @@ -307,6 +347,18 @@ Vop3p::Vop3p(std::string_view mnemonic, const Vop3pMachineInst *inst, ExecuteFn size_ += sizeof(MachineInst); } +void Vop3p::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3p::has_lit_0() { return (((inst_.src0 == 255) && (inst_.src1 != 255)) && (inst_.src2 != 255)); } @@ -444,6 +496,18 @@ Vop3SdstEnc::Vop3SdstEnc(std::string_view mnemonic, const Vop3SdstEncMachineInst size_ += sizeof(MachineInst); } +void Vop3SdstEnc::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3SdstEnc::has_lit_0() { return (((inst_.src0 == 255) && (inst_.src1 != 255)) && (inst_.src2 != 255)); } diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/gfx1250/encodings.h b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/gfx1250/encodings.h index d0697ad7efc7..97115146fc3d 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/gfx1250/encodings.h +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/gfx1250/encodings.h @@ -433,6 +433,7 @@ class Smem : public IsaInstruction { class Vop1 : public IsaInstruction { public: Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool has_lit64(); @@ -494,6 +495,7 @@ class Vopc : public IsaInstruction { class Vop2 : public IsaInstruction { public: Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool has_lit64(); @@ -527,6 +529,7 @@ class Vop2 : public IsaInstruction { class Vop3 : public IsaInstruction { public: Vop3(std::string_view mnemonic, const Vop3MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_and_has_lit_1(); @@ -551,6 +554,7 @@ class Vop3 : public IsaInstruction { class Vop3p : public IsaInstruction { public: Vop3p(std::string_view mnemonic, const Vop3pMachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_and_has_lit_1(); @@ -621,6 +625,7 @@ class Vglobal : public IsaInstruction { class Vop3SdstEnc : public IsaInstruction { public: Vop3SdstEnc(std::string_view mnemonic, const Vop3SdstEncMachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_and_has_lit_1(); diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna1/encodings.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna1/encodings.cpp index 6ad0a9621bfc..ad9595e93233 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna1/encodings.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna1/encodings.cpp @@ -126,6 +126,20 @@ Vop1::Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop1::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop1::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255 && inst_.src0 != 249; @@ -162,6 +176,20 @@ Vop2::Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exe literal_ = reinterpret_cast(inst)[1]; } +void Vop2::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop2::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255 && inst_.src0 != 249; diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna1/encodings.h b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna1/encodings.h index 22b3756c96e7..ce63bb2dce27 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna1/encodings.h +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna1/encodings.h @@ -424,6 +424,7 @@ class Smem : public IsaInstruction { class Vop1 : public IsaInstruction { public: Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); using OpEncoding = Vop1MachineInst; @@ -478,6 +479,7 @@ class Vopc : public IsaInstruction { class Vop2 : public IsaInstruction { public: Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool hasImpliedLiteral(); diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna2/encodings.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna2/encodings.cpp index cb2a3e44e931..4e3b1af07d72 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna2/encodings.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna2/encodings.cpp @@ -126,6 +126,20 @@ Vop1::Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop1::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop1::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255 && inst_.src0 != 249; @@ -162,6 +176,20 @@ Vop2::Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exe literal_ = reinterpret_cast(inst)[1]; } +void Vop2::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop2::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255 && inst_.src0 != 249; diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna2/encodings.h b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna2/encodings.h index 999be99a37f6..2b12636e6541 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna2/encodings.h +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna2/encodings.h @@ -420,6 +420,7 @@ class Smem : public IsaInstruction { class Vop1 : public IsaInstruction { public: Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); using OpEncoding = Vop1MachineInst; @@ -474,6 +475,7 @@ class Vopc : public IsaInstruction { class Vop2 : public IsaInstruction { public: Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool hasImpliedLiteral(); diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3/encodings.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3/encodings.cpp index 9c35ec6b55d2..85e849431bc3 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3/encodings.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3/encodings.cpp @@ -126,6 +126,20 @@ Vop1::Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop1::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop1::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255; } @@ -160,6 +174,20 @@ Vop2::Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exe literal_ = reinterpret_cast(inst)[1]; } +void Vop2::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop2::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255; } @@ -181,6 +209,18 @@ Vop3::Vop3(std::string_view mnemonic, const Vop3MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop3::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3::has_lit_0() { return inst_.src0 == 255 && inst_.src1 != 255 && inst_.src2 != 255; } bool Vop3::has_lit_1() { @@ -221,6 +261,18 @@ Vop3p::Vop3p(std::string_view mnemonic, const Vop3pMachineInst *inst, ExecuteFn size_ += sizeof(MachineInst); } +void Vop3p::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3p::has_lit_0() { return inst_.src0 == 255 && inst_.src1 != 255 && inst_.src2 != 255; } bool Vop3p::has_lit_1() { @@ -388,6 +440,18 @@ Vop3SdstEnc::Vop3SdstEnc(std::string_view mnemonic, const Vop3SdstEncMachineInst size_ += sizeof(MachineInst); } +void Vop3SdstEnc::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3SdstEnc::has_lit_0() { return inst_.src0 == 255 && inst_.src1 != 255 && inst_.src2 != 255; } diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3/encodings.h b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3/encodings.h index 188bc995b552..a1bd57699551 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3/encodings.h +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3/encodings.h @@ -398,6 +398,7 @@ class Smem : public IsaInstruction { class Vop1 : public IsaInstruction { public: Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); using OpEncoding = Vop1MachineInst; @@ -453,6 +454,7 @@ class Vopc : public IsaInstruction { class Vop2 : public IsaInstruction { public: Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool hasImpliedLiteral(); @@ -483,6 +485,7 @@ class Vop2 : public IsaInstruction { class Vop3 : public IsaInstruction { public: Vop3(std::string_view mnemonic, const Vop3MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_has_lit_1(); @@ -505,6 +508,7 @@ class Vop3 : public IsaInstruction { class Vop3p : public IsaInstruction { public: Vop3p(std::string_view mnemonic, const Vop3pMachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_has_lit_1(); @@ -590,6 +594,7 @@ class Flat : public IsaInstruction { class Vop3SdstEnc : public IsaInstruction { public: Vop3SdstEnc(std::string_view mnemonic, const Vop3SdstEncMachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_has_lit_1(); diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3_5/encodings.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3_5/encodings.cpp index 3a62ed884ef1..99713b1f336a 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3_5/encodings.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3_5/encodings.cpp @@ -130,6 +130,20 @@ Vop1::Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop1::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop1::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255; } @@ -164,6 +178,20 @@ Vop2::Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exe literal_ = reinterpret_cast(inst)[1]; } +void Vop2::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop2::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255; } @@ -185,6 +213,18 @@ Vop3::Vop3(std::string_view mnemonic, const Vop3MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop3::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3::has_lit_0() { return inst_.src0 == 255 && inst_.src1 != 255 && inst_.src2 != 255; } bool Vop3::has_lit_1() { @@ -225,6 +265,18 @@ Vop3p::Vop3p(std::string_view mnemonic, const Vop3pMachineInst *inst, ExecuteFn size_ += sizeof(MachineInst); } +void Vop3p::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3p::has_lit_0() { return inst_.src0 == 255 && inst_.src1 != 255 && inst_.src2 != 255; } bool Vop3p::has_lit_1() { @@ -392,6 +444,18 @@ Vop3SdstEnc::Vop3SdstEnc(std::string_view mnemonic, const Vop3SdstEncMachineInst size_ += sizeof(MachineInst); } +void Vop3SdstEnc::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3SdstEnc::has_lit_0() { return inst_.src0 == 255 && inst_.src1 != 255 && inst_.src2 != 255; } diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3_5/encodings.h b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3_5/encodings.h index 599df7c187aa..65034b26cf47 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3_5/encodings.h +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna3_5/encodings.h @@ -413,6 +413,7 @@ class Smem : public IsaInstruction { class Vop1 : public IsaInstruction { public: Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); using OpEncoding = Vop1MachineInst; @@ -468,6 +469,7 @@ class Vopc : public IsaInstruction { class Vop2 : public IsaInstruction { public: Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool hasImpliedLiteral(); @@ -498,6 +500,7 @@ class Vop2 : public IsaInstruction { class Vop3 : public IsaInstruction { public: Vop3(std::string_view mnemonic, const Vop3MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_has_lit_1(); @@ -520,6 +523,7 @@ class Vop3 : public IsaInstruction { class Vop3p : public IsaInstruction { public: Vop3p(std::string_view mnemonic, const Vop3pMachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_has_lit_1(); @@ -605,6 +609,7 @@ class Flat : public IsaInstruction { class Vop3SdstEnc : public IsaInstruction { public: Vop3SdstEnc(std::string_view mnemonic, const Vop3SdstEncMachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_has_lit_1(); diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna4/encodings.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna4/encodings.cpp index acd8322f5a3b..828c9c4cfbd2 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna4/encodings.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna4/encodings.cpp @@ -117,6 +117,20 @@ Vop1::Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop1::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop1::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255; } @@ -151,6 +165,20 @@ Vop2::Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exe literal_ = reinterpret_cast(inst)[1]; } +void Vop2::implicit_uses(RegisterSet &uses) const { + bool sdwa_preserve = + sdwa_dst_sel_ != amdgpu::sdwa::DWORD && sdwa_dst_unused_ == amdgpu::sdwa::UNUSED_PRESERVE; + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (sdwa_preserve || dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop2::default_encoding() { return inst_.src0 != 250 && inst_.src0 != 233 && inst_.src0 != 234 && inst_.src0 != 255; } @@ -172,6 +200,18 @@ Vop3::Vop3(std::string_view mnemonic, const Vop3MachineInst *inst, ExecuteFn exe size_ += sizeof(MachineInst); } +void Vop3::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3::has_lit_0() { return inst_.src0 == 255 && inst_.src1 != 255 && inst_.src2 != 255; } bool Vop3::has_lit_1() { @@ -212,6 +252,18 @@ Vop3p::Vop3p(std::string_view mnemonic, const Vop3pMachineInst *inst, ExecuteFn size_ += sizeof(MachineInst); } +void Vop3p::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3p::has_lit_0() { return inst_.src0 == 255 && inst_.src1 != 255 && inst_.src2 != 255; } bool Vop3p::has_lit_1() { @@ -370,6 +422,18 @@ Vop3SdstEnc::Vop3SdstEnc(std::string_view mnemonic, const Vop3SdstEncMachineInst size_ += sizeof(MachineInst); } +void Vop3SdstEnc::implicit_uses(RegisterSet &uses) const { + bool dpp_partial = inst_.src0 == amdgpu::SRC_DPP && + (dpp_row_mask_ != 0xF || dpp_bank_mask_ != 0xF || + (dpp_bound_ctrl_ == 0 && amdgpu::dpp::dpp_ctrl_produces_oob(dpp_ctrl_))); + if (dpp_partial) + for (int i = 0; i < num_dst_operands(); ++i) + if (const auto *dst = dst_operand(i)) + if (auto ref = dst->to_register_ref()) + if (ref->cls == RegClass::VGPR) + uses.expand(*ref); +} + bool Vop3SdstEnc::has_lit_0() { return inst_.src0 == 255 && inst_.src1 != 255 && inst_.src2 != 255; } diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna4/encodings.h b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna4/encodings.h index 710ab8a86590..6fad3d9bc192 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna4/encodings.h +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/rdna4/encodings.h @@ -430,6 +430,7 @@ class Smem : public IsaInstruction { class Vop1 : public IsaInstruction { public: Vop1(std::string_view mnemonic, const Vop1MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); using OpEncoding = Vop1MachineInst; @@ -485,6 +486,7 @@ class Vopc : public IsaInstruction { class Vop2 : public IsaInstruction { public: Vop2(std::string_view mnemonic, const Vop2MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool default_encoding(); bool has_lit(); bool hasImpliedLiteral(); @@ -515,6 +517,7 @@ class Vop2 : public IsaInstruction { class Vop3 : public IsaInstruction { public: Vop3(std::string_view mnemonic, const Vop3MachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_has_lit_1(); @@ -537,6 +540,7 @@ class Vop3 : public IsaInstruction { class Vop3p : public IsaInstruction { public: Vop3p(std::string_view mnemonic, const Vop3pMachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_has_lit_1(); @@ -634,6 +638,7 @@ class Vglobal : public IsaInstruction { class Vop3SdstEnc : public IsaInstruction { public: Vop3SdstEnc(std::string_view mnemonic, const Vop3SdstEncMachineInst *inst, ExecuteFn exec_fn); + void implicit_uses(RegisterSet &uses) const override; bool has_lit_0(); bool has_lit_1(); bool has_lit_0_has_lit_1(); diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/dpp_sdwa_ops.h b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/dpp_sdwa_ops.h index 187cc6152e19..20e334d60a27 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/dpp_sdwa_ops.h +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/dpp_sdwa_ops.h @@ -198,6 +198,24 @@ inline int dpp_permute(uint32_t dpp_ctrl, int lane, int wf_size, bool &out_of_bo return lane; } +/// @brief Return true if a dpp_ctrl can read across a row/wavefront edge. +/// +/// Such controls produce an out-of-bounds source lane, so with BOUND_CTRL=0 +/// they leave some destination lanes unwritten (old value preserved) -- the +/// shifts, wavefront shifts, and row broadcasts. Rotates, mirrors, quad_perm, +/// row_share and row_xmask always map to valid lanes. Derived from dpp_permute +/// over a full 64-lane wavefront so it stays consistent as controls are added; +/// this is an analysis-time query, not on the execution hot path. +inline bool dpp_ctrl_produces_oob(uint32_t dpp_ctrl) { + for (int lane = 0; lane < 64; ++lane) { + bool oob = false; + (void)dpp_permute(dpp_ctrl, lane, /*wf_size=*/64, oob); + if (oob) + return true; + } + return false; +} + /// @brief Check if a lane is disabled by DPP row/bank masks. /// /// @param lane Lane index. diff --git a/emulation/rocjitsu/tests/analysis/liveness_test.cpp b/emulation/rocjitsu/tests/analysis/liveness_test.cpp index 0ffac41ccd8c..4eaa53c89699 100644 --- a/emulation/rocjitsu/tests/analysis/liveness_test.cpp +++ b/emulation/rocjitsu/tests/analysis/liveness_test.cpp @@ -6,6 +6,7 @@ #include "rocjitsu/code/basic_block.h" #include "rocjitsu/code/code_object.h" #include "rocjitsu/code/patch/instruction_builder.h" +#include "rocjitsu/code/rj_code.h" #include "rocjitsu/isa/arch/amdgpu/cdna3/builders.h" #include "rocjitsu/isa/arch/amdgpu/cdna3/opcodes.h" #include "rocjitsu/isa/arch/amdgpu/cdna4/operand.h" @@ -831,5 +832,315 @@ TEST(InstDefUse, Predicated) { EXPECT_TRUE(idu.has_predicated_def); } +// --- Generated VOP1 SDWA/DPP destination-preserve reads (real decode) --- +// +// SDWA dst_unused:PRESERVE and a partial DPP row/bank mask both keep the old +// vdst value, so the decoded instruction must report vdst as an implicit use. +// InstDefUse is the per-instruction def/use set LivenessAnalysis consumes (it +// calls Instruction::implicit_uses), so a use surfacing here is exactly what +// reaches liveness -- see ImplicitUseIsLiveBeforeInstruction for that step. +// +// CDNA4 VOP1 word0: encoding[31:25]=0x3F, vdst[24:17], op[16:9]=1 (v_mov_b32), +// src0[8:0]=marker (250=SRC_DPP, 249=SRC_SDWA). +constexpr uint32_t kVop1MovWord0Dpp = (0x3Fu << 25) | (5u << 17) | (1u << 9) | 250u; +constexpr uint32_t kVop1MovWord0Sdwa = (0x3Fu << 25) | (5u << 17) | (1u << 9) | 249u; + +std::unique_ptr decode_cdna4(const std::array &words) { + auto decoder = Decoder::create(ROCJITSU_CODE_ARCH_CDNA4); + return std::unique_ptr(decoder ? decoder->decode(words.data()) : nullptr); +} + +// DPP word1 fields (CDNA4): vsrc0[7:0], dpp_ctrl[16:8], bound_ctrl[19], +// bank_mask[27:24], row_mask[31:28]. With full masks, whether vdst is +// preserved depends on bound_ctrl and whether dpp_ctrl crosses a row/wave +// edge: bound_ctrl=0 + an edge-crossing ctrl leaves OOB lanes unwritten (reads +// vdst); bound_ctrl=1 writes a zero source instead (full write); a ctrl that +// never goes OOB is a full write regardless of bound_ctrl. +constexpr uint32_t kDppFullMasks = (0xFu << 28) | (0xFu << 24); +constexpr uint32_t kDppBoundCtrl = (1u << 19); +constexpr uint32_t kDppCtrlRowShr1 = 0x111u << 8; // row_shr:1 -- crosses the row edge +constexpr uint32_t kDppCtrlRowRor1 = 0x121u << 8; // row_ror:1 -- rotates within the row + +TEST(GeneratedInstDefUse, DppPartialRowMaskReadsDestination) { + // DPP word1: row_mask[31:28]=0x7 (partial), bank_mask[27:24]=0xF, vsrc0[7:0]=2. + auto inst = decode_cdna4({kVop1MovWord0Dpp, (0x7u << 28) | (0xFu << 24) | 2u}); + ASSERT_NE(inst, nullptr); + ASSERT_EQ(std::string_view(inst->mnemonic()).substr(0, 9), "v_mov_b32"); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_TRUE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, DppFullRowMaskDoesNotReadDestination) { + // DPP word1: row_mask=0xF, bank_mask=0xF (full), dpp_ctrl=0 (quad_perm, never + // OOB) -> every lane written, no vdst read even with bound_ctrl=0. + auto inst = decode_cdna4({kVop1MovWord0Dpp, (0xFu << 28) | (0xFu << 24) | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_FALSE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, SdwaPreserveReadsDestination) { + // SDWA word1: vsrc0[7:0]=2, dst_sel[10:8]=0 (BYTE_0, != DWORD), + // dst_unused[12:11]=2 (UNUSED_PRESERVE). + auto inst = decode_cdna4({kVop1MovWord0Sdwa, (2u << 11) | (0u << 8) | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, SdwaPadDoesNotReadDestination) { + // SDWA word1: dst_sel[10:8]=0, dst_unused[12:11]=0 (UNUSED_PAD) -> no read. + auto inst = decode_cdna4({kVop1MovWord0Sdwa, (0u << 11) | (0u << 8) | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_FALSE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, DppBoundCtrlZeroEdgeCrossingReadsDestination) { + // Full masks, bound_ctrl=0, row_shr:1 -> row-edge lanes read OOB and are left + // unwritten, preserving vdst. + auto inst = decode_cdna4({kVop1MovWord0Dpp, kDppFullMasks | kDppCtrlRowShr1 | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_TRUE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, DppBoundCtrlOneEdgeCrossingDoesNotReadDestination) { + // Full masks, row_shr:1 but bound_ctrl=1 -> OOB lanes read a zero source and + // are still written, so every lane is defined and vdst is not read. + auto inst = + decode_cdna4({kVop1MovWord0Dpp, kDppFullMasks | kDppBoundCtrl | kDppCtrlRowShr1 | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_FALSE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, DppBoundCtrlZeroRotateDoesNotReadDestination) { + // Full masks, bound_ctrl=0, row_ror:1 -> a rotate never goes OOB, so every + // lane is written and vdst is not read despite bound_ctrl=0. + auto inst = decode_cdna4({kVop1MovWord0Dpp, kDppFullMasks | kDppCtrlRowRor1 | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_FALSE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, Vop1DppPartialMaskReadsFullWidthDestination) { + // v_rcp_f64_e32 writes a VGPR pair (v[6:7]). A partial DPP row mask preserves + // the whole 64-bit destination, so the implicit use must match the width-2 + // def -- not just the low dword. + // CDNA4 VOP1 word0: encoding[31:25]=0x3F, vdst[24:17]=6, op[16:9]=37 + // (v_rcp_f64), src0[8:0]=250 (SRC_DPP). + constexpr uint32_t kVop1RcpF64Word0Dpp = (0x3Fu << 25) | (6u << 17) | (37u << 9) | 250u; + auto inst = decode_cdna4({kVop1RcpF64Word0Dpp, (0x7u << 28) | (0xFu << 24) | 2u}); + ASSERT_NE(inst, nullptr); + ASSERT_EQ(std::string_view(inst->mnemonic()).substr(0, 9), "v_rcp_f64"); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 6, 2})); + EXPECT_TRUE(idu.uses.contains({RegClass::VGPR, 6, 2})); +} + +// --- Generated VOP2 SDWA/DPP destination-preserve reads (real decode) --- +// +// VOP2 shares VOP1's destination-preserve rules: SDWA dst_unused:PRESERVE and a +// partial DPP row/bank mask both keep the old vdst value, so the decoded +// instruction must report vdst as an implicit use (see Vop2::implicit_uses, +// which mirrors Vop1::implicit_uses). These cases mimic the VOP1 tests above but +// exercise the VOP2 encoding path. +// +// CDNA4 VOP2 word0: encoding[31]=0, op[30:25]=1 (v_add_f32), vdst[24:17], +// vsrc1[16:9], src0[8:0]=marker (250=SRC_DPP, 249=SRC_SDWA). The DPP/SDWA word1 +// layouts are identical to VOP1, so the second-word bit fields are reused. +constexpr uint32_t kVop2AddWord0Dpp = (0u << 31) | (1u << 25) | (5u << 17) | (3u << 9) | 250u; +constexpr uint32_t kVop2AddWord0Sdwa = (0u << 31) | (1u << 25) | (5u << 17) | (3u << 9) | 249u; + +TEST(GeneratedInstDefUse, Vop2DppPartialRowMaskReadsDestination) { + // DPP word1: row_mask[31:28]=0x7 (partial), bank_mask[27:24]=0xF, vsrc0[7:0]=2. + auto inst = decode_cdna4({kVop2AddWord0Dpp, (0x7u << 28) | (0xFu << 24) | 2u}); + ASSERT_NE(inst, nullptr); + ASSERT_EQ(std::string_view(inst->mnemonic()).substr(0, 9), "v_add_f32"); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_TRUE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, Vop2DppFullRowMaskDoesNotReadDestination) { + // DPP word1: row_mask=0xF, bank_mask=0xF (full), dpp_ctrl=0 (quad_perm, never + // OOB) -> every lane written, no vdst read even with bound_ctrl=0. + auto inst = decode_cdna4({kVop2AddWord0Dpp, (0xFu << 28) | (0xFu << 24) | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_FALSE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, Vop2SdwaPreserveReadsDestination) { + // SDWA word1: vsrc0[7:0]=2, dst_sel[10:8]=0 (BYTE_0, != DWORD), + // dst_unused[12:11]=2 (UNUSED_PRESERVE). + auto inst = decode_cdna4({kVop2AddWord0Sdwa, (2u << 11) | (0u << 8) | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, Vop2SdwaPadDoesNotReadDestination) { + // SDWA word1: dst_sel[10:8]=0, dst_unused[12:11]=0 (UNUSED_PAD) -> no read. + auto inst = decode_cdna4({kVop2AddWord0Sdwa, (0u << 11) | (0u << 8) | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_FALSE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, Vop2DppBoundCtrlZeroEdgeCrossingReadsDestination) { + // Full masks, bound_ctrl=0, row_shr:1 -> row-edge lanes read OOB and are left + // unwritten, preserving vdst (mirrors the VOP1 case on the VOP2 path). + auto inst = decode_cdna4({kVop2AddWord0Dpp, kDppFullMasks | kDppCtrlRowShr1 | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_TRUE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, Vop2DppBoundCtrlOneEdgeCrossingDoesNotReadDestination) { + // Full masks, row_shr:1 but bound_ctrl=1 -> OOB lanes read zero and are still + // written, so every lane is defined and vdst is not read. + auto inst = + decode_cdna4({kVop2AddWord0Dpp, kDppFullMasks | kDppBoundCtrl | kDppCtrlRowShr1 | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_FALSE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +// --- Generated VOP3 DPP destination-preserve reads (real decode) --- +// +// VOP3 gained DPP on gfx11+ (RDNA3/RDNA4/gfx1250) and has no SDWA, so only the +// partial-DPP path applies. Unlike VOP1/VOP2 the VOP3 vdst field can name an +// SGPR: a VOP3-re-encoded compare (v_cmp_*_e64) writes its lane mask to an SGPR +// through vdst. So Vop3::implicit_uses derives the preserved ref from the +// decoded destination operand rather than assuming VGPR -- these cases exercise +// both a VGPR-dest op and an SGPR-dest compare. VOP3 is not in CDNA, so these +// decode for RDNA4. +// +// RDNA4 VOP3 word0: encoding[31:26]=53, op[25:16], clamp[15], opsel[14:11], +// abs[10:8], vdst[7:0]. word1: src0[8:0]=marker (250=SRC_DPP), src1[17:9]. The +// DPP16 word2 layout matches VOP1/VOP2, so its bit fields are reused. +constexpr uint32_t kVop3Enc = 53u << 26; +constexpr uint32_t kVop3AddF32Op = 259u << 16; // v_add_f32_e64 (VGPR vdst) +constexpr uint32_t kVop3CmpLtF32Op = 17u << 16; // v_cmp_lt_f32_e64 (SGPR vdst) +// word1: src0=SRC_DPP, src1=VGPR3. +constexpr uint32_t kVop3DppWord1 = (3u << 9) | 250u; + +std::unique_ptr decode_rdna4(const std::array &words) { + auto decoder = Decoder::create(ROCJITSU_CODE_ARCH_RDNA4); + return std::unique_ptr(decoder ? decoder->decode(words.data()) : nullptr); +} + +TEST(GeneratedInstDefUse, Vop3DppPartialRowMaskReadsVgprDestination) { + // v_add_f32_e64 (VGPR vdst=5), DPP word2: row_mask=0x7 (partial), bank_mask=0xF. + auto inst = decode_rdna4( + {kVop3Enc | kVop3AddF32Op | 5u, kVop3DppWord1, (0x7u << 28) | (0xFu << 24) | 2u}); + ASSERT_NE(inst, nullptr); + ASSERT_EQ(std::string_view(inst->mnemonic()).substr(0, 9), "v_add_f32"); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_TRUE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, Vop3DppFullRowMaskDoesNotReadDestination) { + // Full masks, dpp_ctrl=0 (quad_perm, never OOB) -> every lane written. + auto inst = decode_rdna4( + {kVop3Enc | kVop3AddF32Op | 5u, kVop3DppWord1, (0xFu << 28) | (0xFu << 24) | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_FALSE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, Vop3DppBoundCtrlZeroEdgeCrossingReadsDestination) { + // Full masks, bound_ctrl=0, row_shr:1 -> row-edge lanes read OOB and are left + // unwritten, preserving the VGPR vdst. + auto inst = decode_rdna4( + {kVop3Enc | kVop3AddF32Op | 5u, kVop3DppWord1, kDppFullMasks | kDppCtrlRowShr1 | 2u}); + ASSERT_NE(inst, nullptr); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 5, 1})); + EXPECT_TRUE(idu.uses.contains({RegClass::VGPR, 5, 1})); +} + +TEST(GeneratedInstDefUse, Vop3CmpDppPartialRowMaskDoesNotReadDestination) { + // v_cmp_lt_f32_e64 writes its lane mask to an SGPR pair via the vdst field + // (s[8:9]). The executor's non-VOPC DPP restore only touches the VGPR file at + // inst_.vdst -- a no-op that writes back the saved value -- and does NOT + // preserve the SGPR mask, which is fully written. So a partial mask reads + // neither the SGPR nor a VGPR, matching implicit_uses filtering to VGPR. + auto inst = decode_rdna4( + {kVop3Enc | kVop3CmpLtF32Op | 8u, kVop3DppWord1, (0x7u << 28) | (0xFu << 24) | 2u}); + ASSERT_NE(inst, nullptr); + ASSERT_EQ(std::string_view(inst->mnemonic()).substr(0, 9), "v_cmp_lt_"); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::SGPR, 8, 2})); + EXPECT_FALSE(idu.uses.contains({RegClass::SGPR, 8, 2})); + EXPECT_FALSE(idu.uses.contains({RegClass::VGPR, 8, 1})); +} + +TEST(GeneratedInstDefUse, Vop3pDppPartialRowMaskReadsDestination) { + // v_pk_add_u16 (VOP3P, VGPR vdst=6). VOP3P gained DPP on gfx11+ and has no + // SDWA, so a partial row mask preserves the packed VGPR dst. + // RDNA4 VOP3P word0: encoding[31:24]=204, op[22:16]=10 (v_pk_add_u16), + // vdst[7:0]=6. word1: src0[8:0]=250 (SRC_DPP), src1[17:9]=3 (VGPR3). + constexpr uint32_t kVop3pAddU16Word0 = (204u << 24) | (10u << 16) | 6u; + constexpr uint32_t kVop3pDppWord1 = (3u << 9) | 250u; + auto inst = decode_rdna4({kVop3pAddU16Word0, kVop3pDppWord1, (0x7u << 28) | (0xFu << 24) | 2u}); + ASSERT_NE(inst, nullptr); + ASSERT_EQ(std::string_view(inst->mnemonic()).substr(0, 12), "v_pk_add_u16"); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 6, 1})); + EXPECT_TRUE(idu.uses.contains({RegClass::VGPR, 6, 1})); +} + +TEST(GeneratedInstDefUse, Vop3SdstEncDppPartialRowMaskReadsOnlyVgprResult) { + // v_add_co_ci_u32_e64 (VOP3_SDST_ENC) writes TWO destinations: a VGPR result + // (v6) and an SGPR carry-out (s[8:9]). The executor's DPP restore preserves + // only the VGPR result (write_vgpr); the SGPR carry is fully written, so only + // the VGPR surfaces as a use -- implicit_uses filters to RegClass::VGPR. + // RDNA4 VOP3_SDST_ENC word0: encoding[31:26]=53, op[25:16]=288, sdst[14:8]=8, + // vdst[7:0]=6. word1: src0=250 (SRC_DPP), src1[17:9]=3, src2[26:18]=10 (carry). + constexpr uint32_t kVop3SdstWord0 = (53u << 26) | (288u << 16) | (8u << 8) | 6u; + constexpr uint32_t kVop3SdstWord1 = (10u << 18) | (3u << 9) | 250u; + auto inst = decode_rdna4({kVop3SdstWord0, kVop3SdstWord1, (0x7u << 28) | (0xFu << 24) | 2u}); + ASSERT_NE(inst, nullptr); + ASSERT_EQ(std::string_view(inst->mnemonic()).substr(0, 14), "v_add_co_ci_u3"); + + InstDefUse idu(*inst); + EXPECT_TRUE(idu.defs.contains({RegClass::VGPR, 6, 1})); + EXPECT_TRUE(idu.defs.contains({RegClass::SGPR, 8, 2})); + EXPECT_TRUE(idu.uses.contains({RegClass::VGPR, 6, 1})); + EXPECT_FALSE(idu.uses.contains({RegClass::SGPR, 8, 2})); +} + } // namespace } // namespace rocjitsu