Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,19 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32
RegisterRun renamed[8];
u32 nrenamed = 0;
std::array<bool, 256> marker_taken{};
std::array<bool, 256> native_occupied{};
{
const u32 renameable = RenameableFields(opcode);
constexpr u32 bits[4]{16, 24, 32, 64};
// (a pair or quad starts at an even or a 4-aligned register: an odd one is one register, R4n+2 at most two)
const auto span_of = [](u32 reg) { return (reg & 1) ? 1U : (reg & 2) ? 2U : 4U; };
for (u32 f = 0; f < 4; ++f) { // what may be native high GPRs, and which are to be renamed
const u32 bit = bits[f], reg = insn.Bits(bit, 8);
// Survey all original operands before rewriting any of them. A marker chosen for
// an early field must not alias a later source (including GPRs below reserved_base).
// Keep the same conservative pair/quad spans used for high-register accesses.
for (u32 k = 0; k < span_of(reg) && reg + k < 255; ++k)
native_occupied[reg + k] = true;
// (ISBEWR's data too: not counted, a mesh program's output held in a high GPR was read from the plain register,
// never written, while what computes it wrote the extended one: vertices past the first 32 came out undefined.
// SUST and SURED's data as well, the same field. (amd-fix, Lichii #149) RED's too: a reduction's data in a high GPR
Expand All @@ -234,7 +240,7 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32
for (u32 m = 216; m + span <= 246 && marker == 255; m += span) {
bool ok = true;
for (u32 k = 0; k < span && ok; ++k)
ok = m + k != 239 && !avoid[m + k] && !marker_taken[m + k] && !native_high[m + k];
ok = m + k != 239 && !avoid[m + k] && !marker_taken[m + k] && !native_occupied[m + k];
if (ok) marker = m;
}
if (marker != 255 && nrenamed < 8) {
Expand Down
85 changes: 85 additions & 0 deletions tests/lower_register_markers_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Synthetic native instructions built from encoding fields, not copied from a game program.
// Check the effective FFMA operands after interpreting the lowering's REGMAP prefixes.
// python -I -B tests/test_lower_register_markers.py
#include <array>
#include <cstdio>
#include <vector>
#include "shader_recompiler/frontend/ampere/lower_to_maxwell.h"

using Shader::Ampere::Instruction;

namespace {
constexpr Instruction kExit{{0x794d, 0, 0x03800000, 0}};

Instruction fma(u32 dst, u32 a, u32 b, u32 c) {
// @PT FFMA.FTZ Rd, Ra, Rb, Rc; register source form, round-to-nearest.
return Instruction{{0x7223U | (dst << 16) | (a << 24), b, c | 0x10000U, 0}};
}

bool check(u32 dst, u32 a, u32 b, u32 c, bool uniform) {
std::vector<Instruction> code;
// Referencing UR5 reserves the emulated uniform bank, so native GPRs >=216 are extended.
// Without a uniform reference, GPRs 216..245 are ordinary GPRs and still cannot be markers.
if (uniform) code.push_back(Instruction{{0x7c02U, 5, 0x08000f00U, 0}}); // MOV R0, UR5
code.push_back(fma(dst, a, b, c));
code.push_back(kExit);
const auto lowered = Shader::Ampere::LowerToMaxwell(code);
if (!lowered) {
std::fprintf(stderr, "register markers: lowering failed: %.*s\n",
int(lowered.error.size()), lowered.error.data());
return false;
}
std::array<u32, 256> mapping{};
for (u64 word : lowered.instructions) {
if ((word >> 51) == (0xefe0ULL >> 3)) {
const u32 runs = (word >> 40) & 3;
for (u32 i = 0; i < runs && i < 2; ++i) {
const u32 number = (word >> (i ? 20 : 0)) & 0xff;
const u32 target = (word >> (i ? 28 : 8)) & 0xff;
const u32 length = ((word >> (36 + 2 * i)) & 3) + 1;
for (u32 k = 0; k < length; ++k) mapping[number + k] = 256 + target + k;
}
continue;
}
if ((word & 0xff80000000000000ULL) == 0x5980000000000000ULL) {
const auto reg = [&](u32 shift) {
const u32 number = (word >> shift) & 0xff;
return mapping[number] ? mapping[number] : number;
};
const auto expected = [&](u32 native) {
return native >= (uniform ? 216U : 246U) ? native + 256 : native;
};
const std::array<u32, 4> got{reg(0), reg(8), reg(20), reg(39)};
const std::array<u32, 4> want{expected(dst), expected(a), expected(b), expected(c)};
if (got != want) {
std::fprintf(stderr,
"register markers: FFMA %u,%u,%u,%u (uniform=%d) became %u,%u,%u,%u; expected %u,%u,%u,%u\n",
dst, a, b, c, int(uniform), got[0], got[1], got[2], got[3], want[0], want[1], want[2], want[3]);
return false;
}
return true;
}
mapping.fill(0); // Prefixes apply to the next non-prefix instruction only.
}
std::fprintf(stderr, "register markers: missing FFMA\n");
return false;
}
} // namespace

int main() {
u32 cases = 0, failures = 0;
for (u32 source = 216; source < 238; ++source) {
for (bool uniform : {false, true}) {
const u32 dst = uniform ? 238 : 248;
failures += !check(dst, 17, source, dst, uniform);
++cases;
failures += !check(dst, source, 17, dst, uniform);
++cases;
}
}
// Two occupied marker-bank blocks, with a repeated destination/addend that must remain shared.
failures += !check(248, 220, 216, 248, false);
++cases;
std::printf("register markers: %u cases, %u failures\n", cases, failures);
return failures ? 1 : 0;
}
1 change: 1 addition & 0 deletions tests/run_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"tests/test_input_background.py": ["--cc", "gcc"],
"nx2/test_import_windows.py": ["--cxx", "g++"],
"tests/test_lower_depbar.py": ["--cxx", "clang++"],
"tests/test_lower_register_markers.py": ["--cxx", "clang++"],
"tests/test_text_speed.py": ["--cc", "gcc"],
"tests/test_upscale_fallback.py": ["--cxx", "g++"],
"tests/test_nvn_buffer_map.py": ["--cc", "gcc"],
Expand Down
28 changes: 28 additions & 0 deletions tests/test_lower_register_markers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Renaming markers must not alias any original operand of a native instruction.

Compiles a synthetic FFMA fixture against the actual Ampere lowering. No game, GPU or SDK.
python -I -B tests/test_lower_register_markers.py [--cxx clang++]
"""
import argparse
import os
import subprocess
import tempfile
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--cxx", default="clang++")
args = parser.parse_args()
with tempfile.TemporaryDirectory(prefix="lower-register-markers-") as tmp:
exe = Path(tmp) / ("lower_register_markers.exe" if os.name == "nt" else "lower_register_markers")
subprocess.run([args.cxx, "-std=c++20", "-O1", "-w",
"-I" + str(ROOT / "gpu/vk/eden"), "-I" + str(ROOT / "gpu/vk/compat"),
str(ROOT / "tests/lower_register_markers_test.cpp"), "-o", str(exe)], check=True)
subprocess.run([str(exe)], check=True)


if __name__ == "__main__":
main()