aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDuck912 <350528965+Duck912@users.noreply.github.com>2026-11-01 21:41:29 -0500
committerDuck912 <350528965+Duck912@users.noreply.github.com>2026-11-01 21:54:48 -0500
commita169391e6019d6356cc0492c5a25cf7dbfc22fa3 (patch)
tree0dce3444db29d490bef16383491d0caeedc20d18
parentef3ae4ae23b2148fac5b7b335c001b2802a9222b (diff)
shader: count only register writes in the callee link check
The check that a native callee keeps its link register read bits 16-23 of every instruction as a destination. RET was exempt, but BRA, NOP and ISETP/FSETP (which write predicates) also have zero there, so callees with an R0 link that compare or branch were rejected. IMAD.WIDE writes a register pair, so its second register now counts as well. Assisted-by: Claude Opus 5.5 (debugging, implementation and drafting) Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
-rw-r--r--src/shader_recompiler/frontend/ampere/lower_to_maxwell.h9
-rw-r--r--src/tests/video_core/ampere_returns.cpp27
2 files changed, 34 insertions, 2 deletions
diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
index 79f98017ae..489d003e60 100644
--- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
+++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
@@ -92,8 +92,13 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
active_link = call_links[call];
}
}
- // RET has no destination GPR; bits 16..23 are reserved, not a write to R0.
- if (active_link && opcode != 0x950 && insn.Bits(16, 8) == *active_link)
+ // RET, BRA, NOP and ISETP/FSETP (predicate outputs) have no destination GPR; bits 16..23
+ // are not a write to R0. IMAD.WIDE writes a register pair.
+ const bool no_destination = opcode == 0x950 || opcode == 0x947 || opcode == 0x918 ||
+ (opcode & 0x1ff) == 0x00c || (opcode & 0x1ff) == 0x00b;
+ const bool pair = opcode == 0x225 || opcode == 0x625 || opcode == 0x825;
+ if (active_link && !no_destination &&
+ (insn.Bits(16, 8) == *active_link || (pair && insn.Bits(16, 8) + 1 == *active_link)))
return fail(index, "native callee overwrites link register");
}
// Scratch values and emulated uniform state occupy reserved high GPRs.
diff --git a/src/tests/video_core/ampere_returns.cpp b/src/tests/video_core/ampere_returns.cpp
index 56b8b374da..163758e07e 100644
--- a/src/tests/video_core/ampere_returns.cpp
+++ b/src/tests/video_core/ampere_returns.cpp
@@ -48,4 +48,31 @@ TEST_CASE("Ampere callees still reject writes to the link", "[video_core][ampere
REQUIRE(lowered.error == "native callee overwrites link register");
REQUIRE(lowered.error_offset == 0x30);
}
+
+TEST_CASE("Ampere callees can compare into predicates with an R0 link", "[video_core][ampere]") {
+ // ISETP's zero destination field is not a write to R0.
+ const std::array<Instruction, 5> code{{
+ {{0x00007802, 0x20, 0x00000f00, 0}},
+ {{0x00007944, 0x10, 0x03c00000, 0}},
+ {{0x0000794d, 0, 0x03800000, 0}},
+ {{0x0100720c, 0x00000002, 0x03f06070, 0}},
+ {{0x00007950, 0xffffffb0, 0x03c3ffff, 0}},
+ }};
+ REQUIRE(static_cast<bool>(LowerToMaxwell(code)));
+}
+
+TEST_CASE("Ampere callees reject a wide multiply over the link", "[video_core][ampere]") {
+ // IMAD.WIDE.U32 R54:R55 writes the R55 link.
+ const std::array<Instruction, 5> code{{
+ {{0x00377802, 0x20, 0x00000f00, 0}},
+ {{0x00007944, 0x10, 0x03c00000, 0}},
+ {{0x0000794d, 0, 0x03800000, 0}},
+ {{0x01367225, 0x00000002, 0x078e00ff, 0}},
+ {{0x37007950, 0xffffffb0, 0x03c3ffff, 0}},
+ }};
+ const auto lowered = LowerToMaxwell(code);
+ REQUIRE(!static_cast<bool>(lowered));
+ REQUIRE(lowered.error == "native callee overwrites link register");
+ REQUIRE(lowered.error_offset == 0x30);
+}
} // namespace