diff options
| author | kraibse <33869366+kraibse@users.noreply.github.com> | 2026-11-02 13:50:54 +0100 |
|---|---|---|
| committer | kraibse <33869366+kraibse@users.noreply.github.com> | 2026-11-02 14:43:01 +0100 |
| commit | c0cb3325e22756edb65c87d33ef298349ade0ad3 (patch) | |
| tree | e3f6d670018b57004915e69d1fc741b4efecb325 /src/shader_recompiler | |
| parent | 9210b8d69d172b5456e90cbd56b8f2bacbd7386b (diff) | |
| parent | a407703f6d29a5ab73328b4ededb12cf29b2758d (diff) | |
Merge nx2 into nx2-experimental
Resolved by area:
- audio feature_support.h: nx2's native revision handling.
- fiber.cpp: ours, which also drops the unused rewind stack.
- host_memory.cpp: ours granule bitmap and HintCold, plus nx2's
fallocate hole punching per whole dirty granule on Linux.
- texture_cache.h: nx2's old-age eviction pass, then our Collect().
- kepler_compute.cpp: shared upscale slots, then Fortune's Weave's,
then the Kirby Air Riders scan.
- nx2_gsv.h: ours, plus nx2's GetBufferMaxResolution and graphics
device ids (1-based).
- nvhost_ctrl_gpu.cpp: engine support flag bits 40-44.
- lower_to_maxwell.h: ours as the base, with nx2's seven lowering
commits applied one at a time. Second texture descriptor buffers
are read bindless in TEX, TLD.SCR (level zero) and SUST; FSEL
keeps its operand modifiers; TXD takes any channel mask.
lower_to_maxwell.h lowers 837 Fortune's Weave and 1183 Mario Kart
World shader dumps with the same rejects as nx2 and ours (12 and 0).
Assisted-by: Claude Sonnet 5.5 (conflict analysis and drafting)
Diffstat (limited to 'src/shader_recompiler')
| -rw-r--r-- | src/shader_recompiler/frontend/ampere/lower_to_maxwell.h | 454 | ||||
| -rw-r--r-- | src/shader_recompiler/ir_opt/position_pass.cpp | 31 |
2 files changed, 387 insertions, 98 deletions
diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h index 4ce374ce61..024ed27ea3 100644 --- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h +++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h @@ -61,7 +61,7 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 for (const auto& instruction : code) { const u32 op = instruction.Bits(0, 12); uses_uniform_bank |= op == 0x3c4 || op == 0x2bd || op == 0x896 || op == 0xf60 || op == 0xc11 || op == 0xd08 || op == 0xc0c || op == 0x986 || op == 0x9a8 || op == 0xc12 || op == 0xc17 || op == 0xe24 || op == 0xc24 || op == 0xd00 || op == 0xd09 || op == 0x9c3 || op == 0xc45 || op == 0x2a4 || op == 0x891 || op == 0x890 || op == 0x290 || op == 0x28c || op == 0x88c || op == 0x287 || op == 0x887 || op == 0x958 || op == 0xab9 || op == 0xabb || op == 0x899 || op == 0xc20 || op == 0xe21 || op == 0xe23 || op == 0xc02 || - op == 0x886 || op == 0x3c2 || op == 0xc82 || op == 0x882 || op == 0x292 || op == 0x892 || op == 0x291 || op == 0x981 || op == 0xc13 || op == 0xc10 || op == 0xc23 || op == 0x526 || op == 0x2be || op == 0x8a4 || op == 0xd05 || (op == 0x984 && instruction.Bits(91, 1)) || op == 0x89c || op == 0x883 || op == 0x2a5 || op == 0xd06 || op == 0xf63 || op == 0xf69 || op == 0xc0b || op == 0x8a5 || op == 0x299 || op == 0xc09 || (op == 0x81c && instruction.Bits(67, 1)); + op == 0x886 || op == 0x3c2 || op == 0xc82 || op == 0x882 || op == 0x292 || op == 0x892 || op == 0x291 || op == 0x981 || op == 0xc13 || op == 0xc10 || op == 0xc23 || op == 0x526 || op == 0x2be || op == 0x8a4 || op == 0xd05 || (op == 0x984 && instruction.Bits(91, 1)) || op == 0x89c || op == 0x883 || op == 0x2a5 || op == 0xd06 || op == 0xf63 || op == 0xf69 || op == 0xc0b || op == 0x8a5 || op == 0x299 || op == 0xc09 || (op == 0x81c && instruction.Bits(67, 1)) || op == 0xc0a; } const auto address_of = [](u32 index) { return 0x50U + index * 8 + ((index + 1) / 3) * 8; }; const auto fail = [&](u32 index, std::string_view reason) { @@ -97,8 +97,13 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 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. @@ -171,6 +176,13 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 }; std::string_view operand_error; const auto emit = [&](u64 word) { result.instructions.push_back(word); }; + // Bound Maxwell texture instructions share one descriptor constant buffer per program. + const auto bind_texture_buffer = [&](u32 binding) { + if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding) + return false; + result.required_texture_buffer = binding; + return true; + }; // P(guard slot) while a uniform guard is swapped in; P6 for lowerings that use it as // scratch (R2UR, MATCH, HFMA2.RELU), which save and restore it with the defaults below. const u32 guard_mask = guard_slot < 7 ? 1U << guard_slot : 0U; @@ -474,6 +486,20 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 emit(base(0x5c98000000000000ULL) | (u64{15} << 39) | (u64{insn.Bits(64, 8)} << 20)); break; } + if (insn.Bits(24, 8) == 255 && insn.words[1] == 255 && (insn.words[2] & ~0x07800affU) == 0x000e0400 && + (!rewritten_uniform || insn.Bits(87, 3) == 7)) { + // IMAD.X RZ, RZ, c, Pn: the high word of a 64-bit add (bit 75 inverts c). + u32 c = insn.Bits(64, 8); + mov_imm(250, 1); + emit(0x5ca0000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{255} << 20) | + (u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42)); + if (insn.Bits(75, 1)) { + emit(0x0400000000000000ULL | predicate | 251 | (u64{c} << 8) | (u64{0xffffffffU} << 20) | (u64{2} << 53)); + c = 251; + } + emit(base(0x5c10000000000000ULL) | (u64{c} << 8) | (u64{250} << 20)); + break; + } if ((insn.words[2] & ~0x2ffU) != 0x078e0000 || (insn.words[1] & ~0xffU)) return fail(index, "native integer register multiply mode"); multiply_low(insn.Bits(24, 8), insn.Bits(32, 8), insn.Bits(64, 8), false); @@ -504,38 +530,87 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 emit(0x5c10000000070000ULL | d_low | (u64{250} << 8) | (u64{255} << 20)); break; } - case 0x825: { // IMAD.WIDE.U32 Rd:Rd+1 = Ra * immediate + Rc:Rc+1 (pointer arithmetic) - const u32 a = insn.Bits(24, 8), b = insn.words[1], c = insn.Bits(64, 8); - const u32 d = static_cast<u32>(dest), c_high = c == 255 ? 255U : c + 1; - if ((insn.words[2] & ~0xffU) != 0x078e0000 || (d & 1) || d >= 246 || a >= 246 || - (c != 255 && ((c & 1) || c >= 246))) - return fail(index, "native wide multiply mode"); - // As UIMAD.WIDE.U32: the high word in R253, the low word in R250, then the 64-bit add. - xmad(250, a, b, 255, false, false, 0, true); - xmad(251, a, b, 250, true, false, 2, true); - xmad(250, a, b, 251, false, true, 1, true); - xmad(252, a, b, 251, true, true, 2, true); + case 0x226: { // IDP.2A.LO.U16.U8: a.lo16 * b.byte0 + a.hi16 * b.byte1 + c + if ((insn.words[2] & ~0xffU) != 0x1000 || (insn.words[1] & ~0xffU)) + return fail(index, "native packed dot-product mode"); + const u32 a = insn.Bits(24, 8), b = insn.Bits(32, 8), c = insn.Bits(64, 8); + for (u32 component = 0; component < 2; ++component) { + emit(0x3828000000000000ULL | predicate | (250 + component) | (u64{b} << 8) | (u64{component * 8} << 20)); + emit(0x0400000000000000ULL | predicate | (250 + component) | (u64{250 + component} << 8) | (u64{0xff} << 20)); + } + xmad(252, a, 250, c, false, false, 0, false); + xmad(static_cast<u32>(dest), a, 251, 252, true, false, 0, false); + break; + } + case 0x225: case 0x227: case 0x625: case 0x825: case 0x827: case 0xa27: { + // IMAD.HI Rd / IMAD.WIDE Rd:Rd+1 = a * b + c:c+1 (.U32 or signed, bit 73), with an optional + // carry-out (bits 81-83). b is a register, an immediate (0x8xx) or a cbuf (0xa27); 0x625 takes + // b from bits 64-71 and adds a cbuf pair. + const bool wide = opcode == 0x225 || opcode == 0x625 || opcode == 0x825; + const bool immediate = opcode == 0x825 || opcode == 0x827, signed_product = insn.Bits(73, 1); + const u32 c = opcode == 0x625 ? 255U : insn.Bits(64, 8), carry_output = insn.Bits(81, 3); + if ((insn.words[2] & ~0x000e02ffU) != 0x07800000 || + ((opcode == 0x225 || opcode == 0x227) && (insn.words[1] & ~0xffU)) || + ((opcode == 0xa27 || opcode == 0x625) && (!valid_cbuf() || insn.Bits(62, 2))) || + (opcode == 0x625 && insn.Bits(38, 16) > 0xfff8) || (c != 255 && (c & 1)) || + (wide && (dest == 255 || (dest & 1))) || (signed_product && carry_output != 7)) + return fail(index, "native high multiply mode"); + const u32 a = insn.Bits(24, 8); + const u32 b = immediate ? insn.words[1] : opcode == 0xa27 ? 254U : insn.Bits(opcode == 0x625 ? 64 : 32, 8); + if (opcode == 0xa27) emit(0x4c98000000000000ULL | predicate | 254 | (u64{15} << 39) | cbuf()); + if (signed_product) { + // The signed high word is the unsigned one minus (a < 0 ? b : 0) + (b < 0 ? a : 0), + // which R253 holds (R252 is free until the multiplication). + const u32 b_register = immediate ? 254U : b; + if (immediate) mov_imm(254, b); + emit(0x3828000000000000ULL | predicate | 253 | (u64{a} << 8) | (u64{31} << 20) | (u64{1} << 48)); + emit(0x5c40000000000000ULL | predicate | 253 | (u64{253} << 8) | (u64{b_register} << 20) | (u64{7} << 48)); + emit(0x3828000000000000ULL | predicate | 252 | (u64{b_register} << 8) | (u64{31} << 20) | (u64{1} << 48)); + emit(0x5c40000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{a} << 20) | (u64{7} << 48)); + emit(0x5c10000000000000ULL | predicate | 253 | (u64{253} << 8) | (u64{252} << 20)); + } + // Base-2^16 long multiplication. Each intermediate sum fits U32. + xmad(250, a, b, 255, false, false, 0, immediate); + xmad(251, a, b, 250, true, false, 2, immediate); + xmad(250, a, b, 251, false, true, 1, immediate); + xmad(252, a, b, 251, true, true, 2, immediate); emit(0x3828000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{16} << 20)); + if (signed_product) + emit(0x5c10000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{253} << 20) | (u64{1} << 48)); + if (!wide && c == 255 && carry_output == 7) { + emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{252} << 20)); + break; + } + // High word in R253 and low word in R250 (as UIMAD.WIDE.U32), then c:c+1 is added. emit(0x5c10000000000000ULL | predicate | 253 | (u64{250} << 8) | (u64{252} << 20)); - xmad(250, a, b, 255, false, false, 0, true); - xmad(251, a, b, 255, true, false, 0, true); - xmad(251, a, b, 251, false, true, 0, true); + xmad(250, a, b, 255, false, false, 0, immediate); + xmad(251, a, b, 255, true, false, 0, immediate); + xmad(251, a, b, 251, false, true, 0, immediate); emit(0x5c18000000000000ULL | predicate | 250 | (u64{251} << 8) | (u64{250} << 20) | (u64{16} << 39)); - emit(0x5c10000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{c} << 20) | (u64{1} << 47)); - emit(0x5c10000000000000ULL | predicate | (d + 1) | (u64{253} << 8) | (u64{c_high} << 20) | (u64{1} << 43)); - emit(0x5c10000000000000ULL | predicate | d | (u64{250} << 8) | (u64{255} << 20)); - break; - } - case 0x827: { // IMAD.HI.U32, captured zero addend - if (insn.words[2] != 0x078e00ff) return fail(index, "native high multiply mode"); - const u32 a = insn.Bits(24, 8), b = insn.words[1]; - // Base-2^16 long multiplication. Each intermediate sum fits U32. - xmad(250, a, b, 255, false, false, 0, true); - xmad(251, a, b, 250, true, false, 2, true); - xmad(250, a, b, 251, false, true, 1, true); - xmad(252, a, b, 251, true, true, 2, true); - emit(0x3828000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{16} << 20)); - emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{252} << 20)); + u32 c_low = c, c_high = c == 255 ? 255U : c + 1; + if (opcode == 0x625) { + emit(0x4c98000000000000ULL | predicate | 252 | (u64{15} << 39) | cbuf()); + emit(0x4c98000000000000ULL | predicate | 254 | (u64{15} << 39) | (cbuf() + (u64{1} << 20))); + c_low = 252; + c_high = 254; + } + const u32 high = wide ? static_cast<u32>(dest) + 1 : static_cast<u32>(dest); + emit(0x5c10000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{c_low} << 20) | (u64{1} << 47)); + if (carry_output == 7) { + emit(0x5c10000000000000ULL | predicate | high | (u64{253} << 8) | (u64{c_high} << 20) | (u64{1} << 43)); + if (wide) emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{255} << 20)); + break; + } + // Maxwell IADD has no .X with .CC: R251 takes the low carry, R254 counts the high word's. + emit(0x5c10000000000000ULL | predicate | 251 | (u64{255} << 8) | (u64{255} << 20) | (u64{1} << 43)); + emit(0x5c10000000000000ULL | predicate | 253 | (u64{253} << 8) | (u64{c_high} << 20) | (u64{1} << 47)); + emit(0x5c10000000000000ULL | predicate | 254 | (u64{255} << 8) | (u64{255} << 20) | (u64{1} << 43)); + emit(0x5c10000000000000ULL | predicate | 253 | (u64{253} << 8) | (u64{251} << 20) | (u64{1} << 47)); + emit(0x5c10000000000000ULL | predicate | 254 | (u64{254} << 8) | (u64{255} << 20) | (u64{1} << 43)); + emit(0x5c98000000000000ULL | predicate | high | (u64{253} << 20) | (u64{15} << 39)); + if (wide) emit(base(0x5c98000000000000ULL) | (u64{250} << 20) | (u64{15} << 39)); + emit(0x5b60000000000000ULL | predicate | 7 | (u64{carry_output} << 3) | (u64{254} << 8) | + (u64{255} << 20) | (u64{7} << 39) | (u64{5} << 49)); break; } case 0xa24: { // IMAD, constant-buffer multiplier @@ -577,6 +652,21 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 } case 0x824: { // Captured low-word IMAD forms; bit 75 negates the addend. const bool negate_c = insn.Bits(75, 1) != 0; + if (insn.words[1] == 1 && (insn.words[2] & ~(0x07800800U | 0xffU)) == 0x000e0600U) { + // IMAD.X Rd, Ra, 1, Rc, Pn: Ra + Rc plus the carry predicate in bits 87-90 (~Rc with bit 75). + u32 c = insn.Bits(64, 8); + mov_imm(252, 1); + emit(0x5ca0000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{255} << 20) | + (u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42)); + if (negate_c) { + emit(0x0400000000000000ULL | predicate | 251 | (u64{c} << 8) | (u64{0xffffffffU} << 20) | (u64{2} << 53)); + c = 251; + } + emit(0x5c10000000000000ULL | predicate | 250 | (u64{insn.Bits(24, 8)} << 8) | + (u64{c} << 20)); + emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{252} << 20)); + break; + } if (insn.words[1] == 1 && (insn.words[2] & 0xfffff700U) == 0x078e0200) { emit(base(0x5c10000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) | (u64{insn.Bits(64, 8)} << 20) | (u64{negate_c} << 48)); @@ -802,34 +892,36 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 (u64{insn.Bits(64, 8)} << 20) | (u64{maxwell} << 28)); break; } - case 0xb6c: { // TXD 2D or ARRAY_2D, four channels; the two destination pairs anywhere. + case 0xb6c: { // TXD 2D or ARRAY_2D, any channels; the two destination pairs anywhere. const u32 binding = insn.Bits(54, 5), offset = insn.Bits(40, 14); const u32 coordinates = insn.Bits(24, 8), derivatives = insn.Bits(32, 8); // Ampere dimensions: 1 = 2D, 5 = ARRAY_2D; Maxwell TXD's are 2 and 3. The coordinates // (x, y[, layer]) and the four gradients sit in the same registers for both. const u32 dimension = insn.Bits(61, 3); const u32 high = insn.Bits(64, 8); // destination of the third and fourth channels + const u32 mask = insn.Bits(72, 4), channels = static_cast<u32>(std::popcount(mask)); // Bit 90 is forwarded below (Maxwell's NODEP); Splatoon Raiders sets it. - if ((insn.words[2] & ~(0xffU | (1U << 26))) != 0x001e0f00 || insn.Bits(59, 2) || + if ((insn.words[2] & ~(0xfffU | (1U << 26))) != 0x001e0000 || mask == 0 || insn.Bits(59, 2) || (dimension != 1 && dimension != 5) || binding >= 18 || offset > 0x1fff || - (dest & 1) || dest >= 248 || (high & 1) || (high >= 248 && high != 255) || + (channels > 1 && (dest & 1)) || dest >= 248 || + (channels > 2 ? high != 255 && ((high & 1) || high >= 248) : high != 255) || coordinates >= 248 || derivatives >= 247) return fail(index, "native gradient texture mode"); if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding) return fail(index, "native texture descriptor buffer conflict"); result.required_texture_buffer = binding; - // Maxwell writes four consecutive registers: when the second pair is elsewhere (Splatoon + // Maxwell writes consecutive registers: when the second pair is elsewhere (Splatoon // Raiders: R12/R18, R18/R44), write them to scratch and move each pair into place. - const bool consecutive = high == dest + 2; + const bool consecutive = channels <= 2 || high == dest + 2; const u64 target = consecutive ? dest : 248; emit(0xde00000000000000ULL | predicate | target | (u64{coordinates} << 8) | - (u64{derivatives} << 20) | (u64{dimension == 5 ? 3U : 2U} << 28) | (u64{15} << 31) | + (u64{derivatives} << 20) | (u64{dimension == 5 ? 3U : 2U} << 28) | (u64{mask} << 31) | (u64{offset} << 36) | (u64{insn.Bits(90, 1)} << 49) | (u64{7} << 51)); if (!consecutive) { - for (u32 i = 0; i < 2; ++i) { - emit(0x5c98000000000000ULL | predicate | (dest + i) | (u64{15} << 39) | (u64{248 + i} << 20)); - if (high != 255) - emit(0x5c98000000000000ULL | predicate | (high + i) | (u64{15} << 39) | (u64{250 + i} << 20)); + for (u32 i = 0; i < channels; ++i) { + if (i >= 2 && high == 255) break; + emit(0x5c98000000000000ULL | predicate | (i < 2 ? dest + i : high + i - 2) | + (u64{15} << 39) | (u64{248 + i} << 20)); } } break; @@ -1022,14 +1114,20 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 const u32 binding = insn.Bits(54, 5), offset = insn.Bits(40, 14); const u32 data = insn.Bits(32, 8), coordinate = insn.Bits(24, 8); const u32 dimension = insn.Bits(61, 3); - if (insn.words[2] != 0x00100f00 || dimension < 3 || dimension > 5 || + const bool strong = insn.words[2] == 0x0010af00; // .STRONG.GPU: fenced on both sides + if ((insn.words[2] != 0x00100f00 && !strong) || dimension < 3 || dimension > 5 || binding >= 18 || offset > 0x1fff || insn.Bits(59, 2) || data > 252 || coordinate > (dimension >= 4 ? 252U : 253U)) return fail(index, "native formatted surface store mode"); - if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding) - return fail(index, "native surface descriptor buffer conflict"); - result.required_texture_buffer = binding; + // A second descriptor buffer: SUST.B with the handle, loaded from that buffer, in R250. + const bool bindless = !bind_texture_buffer(binding); + if (bindless) { + emit(0x4c98000000070000ULL | 250 | (u64{15} << 39) | (u64{offset} << 20) | (u64{binding} << 34)); + } + if (strong) emit(0xef98000000000000ULL | predicate | (u64{1} << 8)); // MEMBAR.GL emit(0xeb20000000000000ULL | predicate | data | (u64{coordinate} << 8) | - (u64{15} << 20) | (u64{dimension} << 33) | (u64{offset} << 36) | (u64{1} << 51)); + (u64{15} << 20) | (u64{dimension} << 33) | + (bindless ? u64{250} << 39 : (u64{offset} << 36) | (u64{1} << 51))); + if (strong) emit(0xef98000000000000ULL | predicate | (u64{1} << 8)); break; } case 0x59f: { // SURED.D.2D.ADD.STRONG.GPU.IGN U32, direct descriptor (title 1's race compute). @@ -1052,21 +1150,27 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 emit(base(0x3828000000000000ULL) | (u64{insn.words[1]} << 8) | (u64{1} << 39) | (u64{1} << 40)); break; - case 0x219: case 0xc19: { // U32 register (or uniform, 0xc19) shifts with a zero word outside the selected result. + case 0x219: case 0xa19: case 0xc19: { // U32 register (or cbuf, 0xa19, or uniform, 0xc19) shifts with a zero word outside the selected result. const u32 low = insn.Bits(24, 8), high = insn.Bits(64, 8); - const u32 shift = opcode == 0xc19 ? uniform_register(insn.Bits(32, 8)) : insn.Bits(32, 8); + const bool constant = opcode == 0xa19; + // A constant shift amount is loaded into R251 once the form is known. + const u32 shift = opcode == 0xc19 ? uniform_register(insn.Bits(32, 8)) : constant ? 251U : insn.Bits(32, 8); + const bool shift_operand = constant ? valid_cbuf() && !insn.Bits(62, 2) : insn.words[1] <= 255; + const auto load_shift = [&] { if (constant) materialize(2, 0, 251, false, false); }; const bool right = insn.Bits(76, 1) != 0, high_result = insn.Bits(80, 1) != 0; // SHF.R.U32 of the low word with a zero high word: a plain logical right shift. if (right && !high_result && high == 255 && insn.Bits(73, 2) == 3 && shift != ~0U && - insn.words[1] <= 255 && + shift_operand && (insn.words[2] & ~(0x000118ffU | (opcode == 0xc19 ? 0x08000000U : 0U))) == 0x00000600U) { + load_shift(); emit(base(0x5c28000000000000ULL) | (u64{low} << 8) | (u64{shift} << 20)); break; } - if (insn.words[1] > 255 || insn.Bits(73, 2) != 3 || shift == ~0U || + if (!shift_operand || insn.Bits(73, 2) != 3 || shift == ~0U || (insn.words[2] & ~(0x000118ffU | (opcode == 0xc19 ? 0x08000000U : 0U))) != 0x00000600U || (right ? (!high_result || low != 255) : (high_result || high != 255))) return fail(index, "native register shift mode"); + load_shift(); emit(base(right ? 0x5cf8000000000000ULL : 0x5bf8000000000000ULL) | (u64{right ? high : 255U} << 8) | (u64{shift} << 20) | (u64{right ? 255U : low} << 39) | (u64{insn.Bits(75, 1)} << 50)); @@ -1105,8 +1209,8 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 if (insn.words[1] || insn.words[2]) return fail(index, "native error barrier mode"); emit(0xef98000000000000ULL | predicate | (u64{1} << 8)); break; - case 0xb1d: // BAR.SYNC 0, full workgroup. - if (insn.words[1] || insn.words[2] || native_predicate != 7) + case 0xb1d: // BAR.SYNC 0 (bit 80: .DEFER_BLOCKING), full workgroup. + if (insn.words[1] || (insn.words[2] & ~0x10000U) || native_predicate != 7) return fail(index, "native workgroup barrier mode"); emit(0xf0a8000000000000ULL | predicate | (u64{1} << 43) | (u64{1} << 44) | (u64{1} << 39) | (u64{7} << 39)); @@ -1376,10 +1480,17 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 // The uniform base sits where a register source would (bits 32-37). const u32 uniform_address = insn.Bits(38, 2) ? ~0U : uniform_register(insn.Bits(32, 6)); - if ((insn.words[2] & ~(0x04000000U | 0x3fU | 0xe00U | 0x2000U)) != 0x081e8100 || + // desc[URn][Ra.64]: the uniform pair is a memory (cache policy) descriptor and + // Ra the whole address. + const bool descriptor_address = (insn.words[2] & ~(0x3fU | 0xe00U)) == 0x0c1e1100; + if ((!descriptor_address && + (insn.words[2] & ~(0x04000000U | 0x3fU | 0xe00U | 0x2000U)) != 0x081e8100) || uniform_address == ~0U || (uniform_address & 1)) return fail(index, "native global load uniform addressing mode"); - if (address == 255) { + if (descriptor_address) { + if ((address & 1) || address >= 246) + return fail(index, "native global load register address pair"); + } else if (address == 255) { address = uniform_address; } else { if (insn.Bits(90, 1) && ((address & 1) || address >= 246)) @@ -1412,10 +1523,16 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 bool extended = insn.Bits(72, 1) != 0; if (opcode == 0x986) { const u32 uniform_address = uniform_register(insn.Bits(64, 6)); - if ((insn.words[2] & ~(0x04000000U | 0x3fU | 0xe00U)) != 0x0810a100 || + // desc[URn][Ra.64]: the uniform pair is a memory (cache policy) descriptor and + // Ra the whole address. + const bool descriptor_address = (insn.words[2] & ~(0x3fU | 0xe00U)) == 0x0c101100; + if ((!descriptor_address && (insn.words[2] & ~(0x04000000U | 0x3fU | 0xe00U)) != 0x0810a100) || uniform_address == ~0U || (uniform_address & 1) || !extended) return fail(index, "native global store uniform addressing mode"); - if (address == 255) address = uniform_address; + if (descriptor_address) { + if ((address & 1) || address >= 246) + return fail(index, "native global store register address pair"); + } else if (address == 255) address = uniform_address; else { if (insn.Bits(90, 1) && ((address & 1) || address >= 254)) return fail(index, "native global store register address pair"); @@ -1438,13 +1555,29 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 return fail(index, "native fragment discard mode"); emit(0xe33000000000000fULL | predicate); break; + case 0x311: { // F2I.FTZ.U64.TRUNC, register source, destination pair + if (insn.words[2] != 0x0021d800 || (insn.words[1] & ~0xffU) || insn.Bits(24, 8) || + (dest & 1) || dest >= 246) + return fail(index, "native unsigned 64-bit float conversion mode"); + emit(base(0x5cb0000000000000ULL) | (u64{3} << 8) | (u64{2} << 10) | + (u64{insn.Bits(32, 8)} << 20) | (u64{3} << 39) | (u64{1} << 44)); + break; + } + case 0x312: { // I2F.F32.(U|S)64, register pair source; sign (74) and rounding (78-79) as Maxwell's + if ((insn.words[2] & ~0xc400U) != 0x00301000 || (insn.words[1] & ~0xffU) || insn.Bits(24, 8) || + (insn.Bits(32, 8) & 1) || insn.Bits(32, 8) >= 254) + return fail(index, "native 64-bit integer-to-float mode"); + emit(base(0x5cb8000000000000ULL) | (u64{2} << 8) | (u64{3} << 10) | (u64{insn.Bits(74, 1)} << 13) | + (u64{insn.Bits(32, 8)} << 20) | (u64{insn.Bits(78, 2)} << 39)); + break; + } case 0x305: case 0xb05: case 0xd05: { // F2I NTZ, register/constant-buffer/uniform source const u32 mode = insn.words[2] & ~(opcode == 0xd05 ? 0x08000000U : 0U); const bool half = opcode == 0x305 && mode == 0x0010f000; const bool small = mode == 0x00212800; const bool floor = (mode & ~0x10100U) == 0x00207000; const bool nearest = (mode & ~0x10100U) == 0x00203000; - if ((!half && !small && !floor && !nearest && (mode & ~0x100U) != 0x0021f000) || + if ((!half && !small && !floor && !nearest && (mode & ~0x10100U) != 0x0020f000) || (opcode != 0xb05 && (insn.words[1] & 0xffffff00U)) || (opcode == 0xd05 && uniform_register(insn.Bits(32, 8)) == ~0U)) { return fail(index, "native float-to-integer conversion mode"); @@ -1548,9 +1681,27 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 return fail(index, "native texel fetch mode or descriptor binding"); } const u32 binding = insn.Bits(54, 5); - if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding) - return fail(index, "native texture descriptor buffer conflict"); - result.required_texture_buffer = binding; + if (!bind_texture_buffer(binding)) { + if (level || offsets) + return fail(index, "native texture descriptor buffer conflict"); + // A second descriptor buffer: TLD.B (LZ) with the coordinates in R250.. (array + // index or Z after Ra) and the handle, loaded from that buffer, in R253. + const auto copy = [&](u32 target, u32 source) { + emit(0x5c98000000000000ULL | predicate | target | (u64{15} << 39) | (u64{source} << 20)); + }; + copy(250, source_a); + if (array || volume) copy(251, source_a + 1); + if (dimension != 0) copy(array || volume ? 252 : 251, source_b); + emit(0x4c98000000070000ULL | 253 | (u64{15} << 39) | (u64{insn.Bits(40, 14)} << 20) | + (u64{binding} << 34)); + emit(0xdd00000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{253} << 20) | + (u64{array ? 3U : volume ? 4U : dimension == 0 ? 0U : 2U} << 28) | (u64{mask} << 31) | + (u64{insn.Bits(90, 1)} << 49) | (u64{7} << 51)); + const u32 components = static_cast<u32>(std::popcount(mask)); + for (u32 i = 0; i < components; ++i) + copy(i < 2 || !split ? static_cast<u32>(dest) + i : second_dest + (i - 2), 250 + i); + break; + } if (array && level) { // TLDS has no array form with a level: TLD takes (layer, x, y) from Ra and the level // from Rb (layer, x in Ra, Ra+1; y, level in Rb, Rb+1 here). Results come out packed, @@ -1712,12 +1863,11 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 case 0xb60: case 0xf60: case 0x361: { // TEX; 0xf60: handle in a uniform register; 0x361: TEX.B // Bindless forms become Maxwell TEX.B, the handle first among the meta registers (the // texture pass tracks it back to its constant buffer words). 0x361 lists the handle - // right after the coordinates, then the LOD (see general_tex); forms with offsets, - // whose place next to the handle is unverified, are not taken. + // right after the coordinates, then the LOD and the offsets (see general_tex). const bool bindless = opcode != 0xb60; const u32 handle = opcode == 0xf60 ? uniform_register(insn.Bits(40, 6)) : 255; if (bindless && ((opcode == 0xf60 && (handle == ~0U || handle >= 246 || insn.Bits(46, 13))) || - (opcode == 0x361 && (insn.Bits(40, 19) || insn.Bits(87, 3) > 3 || insn.Bits(76, 1))))) + (opcode == 0x361 && (insn.Bits(40, 19) || insn.Bits(87, 3) > 3)))) return fail(index, "native bindless texture mode"); const u32 mask = insn.Bits(72, 4); const u32 second_dest = insn.Bits(64, 8); @@ -1733,7 +1883,7 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 insn.Bits(59, 1) || insn.Bits(40, 14) > 0x1fff || insn.Bits(54, 5) != 2 || (source_a & 1) || (source_b & 1) || source_a >= 248 || source_b >= 248 || dest >= 248) return fail(index, "native array depth comparison sample mode"); - result.required_texture_buffer = 2; + if (!bind_texture_buffer(2)) return fail(index, "native texture descriptor buffer conflict"); for (u32 i = 0; i < 3; ++i) { const u32 source = i < 2 ? source_a + i : source_b; emit(0x5c98000000000000ULL | predicate | (250 + i) | @@ -1767,7 +1917,9 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 const u32 handle_slot = opcode == 0x361 ? 1 : 0; const u32 count = coordinates + handle_slot + (has_lod ? 1 : 0) + (offsets ? 1 : 0) + (depth_compare ? 1 : 0); - const u32 in_a = count <= 4 ? (count + 1) / 2 : opcode == 0x361 ? coordinates : 4; + // Unpacked 0x361 (bit 60 clear): Ra holds the coordinates, Rb the handle and the rest. + const u32 in_a = opcode == 0x361 && !insn.Bits(60, 1) ? coordinates + : count <= 4 ? (count + 1) / 2 : opcode == 0x361 ? coordinates : 4; const auto value = [&](u32 i) -> u32 { if (i < in_a) return source_a == 255 ? 255U : source_a + i; return source_b == 255 ? 255U : source_b + (i - in_a); @@ -1777,12 +1929,11 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 return false; const u32 texture_handle = opcode == 0x361 ? value(coordinates) : handle; if (bindless && texture_handle >= 246) return false; - if (!bindless) { - const u32 binding = insn.Bits(54, 5); - if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding) - return false; - result.required_texture_buffer = binding; - } + // A second descriptor buffer is read bindless: TEX.B takes the handle, loaded from + // that buffer, as its first meta register. + const u32 binding = insn.Bits(54, 5); + const bool second_buffer = !bindless && !bind_texture_buffer(binding); + const bool tex_b = bindless || second_buffer; const auto copy = [&](u32 target, u32 source) { emit(0x5c98000000000000ULL | predicate | target | (u64{15} << 39) | (u64{source} << 20)); }; @@ -1791,10 +1942,13 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 u32 meta = 246; u32 next = coordinates + handle_slot; if (bindless) copy(meta++, texture_handle); + else if (second_buffer) + emit(0x4c98000000070000ULL | meta++ | (u64{15} << 39) | (u64{insn.Bits(40, 14)} << 20) | + (u64{binding} << 34)); if (has_lod) copy(meta++, value(next++)); if (offsets) copy(meta++, value(next++)); if (depth_compare) copy(meta++, value(next++)); - if (bindless) { + if (tex_b) { // Maxwell TEX.B: AOFFI at 36 and the LOD mode at 37 instead of 54 and 55. emit(0xde80000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{246} << 20) | (u64{maxwell_type[dimension]} << 28) | (u64{mask} << 31) | (u64{offsets} << 36) | @@ -1850,11 +2004,11 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 dest == 255 || (mask & (mask - 1) && ((dest & 1) || dest >= 254)) || (split && mask == 15 && ((second_dest & 1) || second_dest >= 254)) || (depth_compare && (dimension != 1 || split || mask != 1 || (source_a & 1) || source_a >= 249)) || - (dimension == 5 && ((source_a & 1) || source_a >= 249))) { + (dimension == 5 && ((source_a & 1) || source_a >= 249)) || bindless || + !bind_texture_buffer(2)) { if (!general_tex()) return fail(index, "native texture mode or descriptor binding"); break; } - result.required_texture_buffer = 2; emit((0xd800000000000000ULL & ~(u64{insn.Bits(79, 1)} << 59)) | predicate | dest | (u64{insn.Bits(24, 8)} << 8) | (u64{insn.Bits(32, 8)} << 20) | (u64{insn.Bits(64, 8)} << 28) | (u64{insn.Bits(40, 14)} << 36) | (u64{insn.Bits(90, 1)} << 49) | @@ -2010,12 +2164,22 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 break; } case 0x209: case 0x809: case 0xa09: case 0xc09: { // FMNMX, including a uniform source - if (insn.Bits(74, 6) || insn.Bits(81, 6) || (opcode != 0xc09 && insn.Bits(91, 1))) + if (insn.Bits(74, 6) || insn.Bits(82, 5) || (opcode != 0xc09 && insn.Bits(91, 1))) return fail(index, "native floating min/max mode"); const u32 a = operand(0), b = operand(1); + // .NAN (bit 81): an unordered pair gives the canonical NaN. R252 = ~0 when both are numbers. + const bool propagate_nan = insn.Bits(81, 1); + if (propagate_nan) + emit(0x5800000000000000ULL | predicate | 252 | (u64{a} << 8) | (u64{b} << 20) | (u64{7} << 39) | (u64{7} << 48)); emit(base(0x5c60000000000000ULL) | (u64{a} << 8) | (u64{b} << 20) | (u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42) | (u64{insn.Bits(80, 1)} << 44)); + if (propagate_nan) { + emit(base(0x5c40000000000000ULL) | (dest << 8) | (u64{252} << 20) | (u64{7} << 48)); + emit(0x0400000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{0xffffffffU} << 20) | (u64{2} << 53)); + emit(0x0400000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{0x7fffffffU} << 20)); + emit(base(0x5c40000000000000ULL) | (dest << 8) | (u64{252} << 20) | (u64{1} << 41) | (u64{7} << 48)); + } break; } case 0x308: case 0xb08: case 0xd08: { // MUFU (register, constant or uniform register source) @@ -2139,13 +2303,15 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 } case 0x207: case 0x807: case 0xa07: case 0x208: case 0x808: case 0xa08: { // Bitwise SEL / FSEL - const u32 a = operand(0, false), b = operand(1, false); + // FSEL applies the floating operand modifiers (negate/absolute) before selecting. + const bool floating = (opcode & 0x1ff) == 0x008; + const u32 a = operand(0, floating), b = operand(1, floating); emit(base(0x5ca0000000000000ULL) | (u64{a} << 8) | (u64{b} << 20) | (u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42)); break; } - case 0x20a: case 0x80a: case 0xa0a: { // FSET.BF, combined with Pc (bits 87-90) by bits 74-75 - if (insn.Bits(64, 8) || insn.Bits(74, 2) == 3 || insn.Bits(81, 6) || insn.Bits(91, 1)) + case 0x20a: case 0x80a: case 0xa0a: case 0xc0a: { // FSET.BF, combined with Pc (bits 87-90) by bits 74-75 (0xc0a: uniform b) + if (insn.Bits(64, 8) || insn.Bits(74, 2) == 3 || insn.Bits(81, 6) || (opcode != 0xc0a && insn.Bits(91, 1))) return fail(index, "native floating comparison output mode"); const u32 a = operand(0), b = operand(1); emit(base(0x5800000000000000ULL) | (u64{a} << 8) | (u64{b} << 20) | @@ -2255,15 +2421,19 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 } case 0x217: case 0x817: case 0xa17: case 0xc17: { // IMNMX register, immediate, cbuf or uniform operand. if ((insn.words[2] & ~(0x04000200U | (opcode == 0xc17 ? 0x08000000U : 0U))) != 0x03800000 || - (opcode != 0xa17 && insn.words[1] > (opcode == 0x817 ? 0x7ffffU : 255U)) || + (opcode == 0x817 && insn.words[1] > 0x7ffffU && insn.words[1] < 0xfff80000U) || + (opcode != 0xa17 && opcode != 0x817 && insn.words[1] > 255U) || (opcode == 0xa17 && (!valid_cbuf() || insn.Bits(62, 2)))) return fail(index, "native integer min/max mode"); + // An immediate is a signed 20-bit value: 19 bits at 20, the sign in bit 56. const u32 source = opcode == 0xc17 ? uniform_register(insn.words[1]) : opcode == 0xa17 ? operand(1, false) + : opcode == 0x817 ? insn.words[1] & 0x7ffffU : insn.words[1]; if (source == ~0U) return fail(index, "native uniform min/max operand"); emit(base(opcode == 0x817 ? 0x3820000000000000ULL : 0x5c20000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) | (u64{source} << 20) | + (u64{opcode == 0x817 ? insn.words[1] >> 31 : 0U} << 56) | (u64{insn.Bits(87, 4)} << 39) | (u64{insn.Bits(73, 1)} << 48)); break; } @@ -2293,6 +2463,20 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 break; } case 0xa11: { // LEA with constant base and carry predicate. + if ((insn.words[2] & ~(0x07800000U | 0x0000f800U)) == 0x000f06ffU && !insn.Bits(62, 2) && + insn.Bits(75, 5) != 0) { + // LEA.HI.X.SX32, no high source: (sign-extended Ra << shift) >> 32, plus the + // constant high word and the carry predicate (bits 87-90). + const u32 b = operand(1, false); + emit(0x3828000000000000ULL | predicate | 250 | (u64{insn.Bits(24, 8)} << 8) | + (u64{32 - insn.Bits(75, 5)} << 20) | (u64{1} << 48)); + mov_imm(252, 1); + emit(0x5ca0000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{255} << 20) | + (u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42)); + emit(0x5c10000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{b} << 20)); + emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{252} << 20)); + break; + } if (insn.Bits(72, 1) || insn.Bits(74, 1) || insn.Bits(80, 1) || insn.Bits(81, 3) == 7 || insn.Bits(62, 2)) return fail(index, "native constant LEA carry mode"); @@ -2303,23 +2487,39 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 (dest << 8) | (u64{b} << 20) | (u64{7} << 39) | (u64{1} << 49)); break; } - case 0x211: case 0x811: case 0xc11: { // LEA low word (0xc11: uniform Rb), no carry result + case 0x211: case 0x811: case 0xc11: { // LEA low word (0xc11: uniform Rb) if (insn.Bits(80, 1)) { - // LEA.HI Rd, Ra, Rb (or an immediate, 0x811), RZ, s: Rb + high word of (Ra << s); - // bit 73 sign-extends Ra. - if (insn.Bits(72, 1) || insn.Bits(74, 1) || insn.Bits(81, 3) != 7 || - insn.Bits(64, 8) != 255 || (opcode == 0x211 && insn.Bits(62, 2))) + // LEA.HI Rd, Ra, Rb (or an immediate, 0x811), Rc, s: Rb + high word of (Rc:Ra << s); + // bit 73 sign-extends Ra (Rc is RZ) and .X (bit 74) adds the carry predicate in bits 87-90. + const bool extended = insn.Bits(74, 1) != 0; + const u32 high = insn.Bits(64, 8); + if (insn.Bits(72, 1) || insn.Bits(81, 3) != 7 || + (high != 255 && insn.Bits(73, 1)) || (opcode == 0x211 && insn.Bits(62, 2)) || + (opcode == 0xc11 && (insn.words[1] & ~0x3fU))) return fail(index, "native high effective address mode"); - u32 b = insn.Bits(32, 8); + u32 b = opcode == 0xc11 ? uniform_register(insn.Bits(32, 8)) : insn.Bits(32, 8); + if (b == ~0U) return fail(index, "native uniform LEA register"); if (opcode == 0x811) { b = insn.words[1] ? 251U : 255U; if (b == 251) mov_imm(251, insn.words[1]); } const u64 shift = (u64{32 - insn.Bits(75, 5)} << 20) | (u64{insn.Bits(73, 1)} << 48); - if (b == 255) { - emit(base(0x3828000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) | shift); + // The high word is a right shift of Ra, or with Rc a left funnel shift (SHF.L). + const auto high_word = [&](u64 target) { + if (high == 255) emit(target | 0x3828000000000000ULL | (u64{insn.Bits(24, 8)} << 8) | shift); + else emit(target | 0x36f8000000000000ULL | (u64{insn.Bits(24, 8)} << 8) | (u64{high} << 39) | + (u64{insn.Bits(75, 5)} << 20)); + }; + if (b == 255 && !extended) { + high_word(base(0)); } else { - emit(0x3828000000000000ULL | predicate | 250 | (u64{insn.Bits(24, 8)} << 8) | shift); + high_word(predicate | 250); + if (extended) { + mov_imm(252, 1); + emit(0x5ca0000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{255} << 20) | + (u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42)); + emit(0x5c10000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{252} << 20)); + } emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{b} << 20)); } break; @@ -2327,12 +2527,13 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 // A carry out (bits 81-83) is the low half of a 64-bit address; IADD3.X adds it next. const u32 carry_out = insn.Bits(81, 3); if (insn.Bits(72, 1) || insn.Bits(74, 1) || insn.Bits(80, 1) || - (carry_out != 7 && (opcode != 0x211 || insn.Bits(63, 1) || (rewritten_uniform && carry_out > 1))) || - (opcode == 0x211 && insn.Bits(62, 1)) || - (opcode == 0xc11 && (insn.Bits(62, 2) || uniform_register(insn.Bits(32, 8)) == ~0U))) + (carry_out != 7 && ((opcode != 0x811 && insn.Bits(63, 1)) || + (rewritten_uniform && (opcode != 0x211 || carry_out > 1)))) || + (opcode == 0x211 && insn.Bits(62, 1)) || (opcode == 0xc11 && (insn.words[1] & ~0x8000003fU))) return fail(index, "native LEA carry/high mode"); // 0x211 bit 63 negates Rb (LEA Rd, Ra, -Rb, s), as Maxwell ISCADD's bit 48. u32 b = opcode == 0xc11 ? uniform_register(insn.Bits(32, 8)) : insn.Bits(32, 8); + if (b == ~0U) return fail(index, "native uniform LEA register"); if (opcode == 0x811) { mov_imm(251, insn.words[1]); b = 251; } if (carry_out != 7) { // The sum in R250, carry = sum < Rb (unsigned), as IADD3's carry out; a uniform @@ -2352,7 +2553,7 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 } emit(base(0x5c18000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) | (u64{b} << 20) | (u64{insn.Bits(75, 5)} << 39) | - (u64{opcode == 0x211 ? insn.Bits(63, 1) : 0U} << 48)); + (u64{opcode != 0x811 ? insn.Bits(63, 1) : 0U} << 48)); break; } case 0x210: case 0x810: case 0xa10: case 0xc10: { // IADD3 with zero third source and no carry outputs @@ -2360,7 +2561,7 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 // a carry-in predicate (bits 87-89, negate 90). Uniform forms keep UPn in R239 bit 6-n. if ((opcode == 0x210 || opcode == 0x810) && insn.Bits(64, 8) == 255 && !insn.Bits(73, 1) && !insn.Bits(75, 2) && insn.Bits(84, 3) == 7 && insn.Bits(77, 4) == 15 && !insn.Bits(72, 1) && - (insn.Bits(81, 3) != 7 || insn.Bits(74, 1))) { + (insn.Bits(81, 3) != 7 || insn.Bits(74, 1)) && (opcode == 0x810 || !insn.Bits(63, 1))) { const bool carry_in = insn.Bits(74, 1) != 0; const u32 carry_out = insn.Bits(81, 3); const u32 a = insn.Bits(24, 8); @@ -2403,9 +2604,65 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{251} << 20)); break; } - if (opcode == 0xa10 && insn.Bits(81, 3) != 7) { + // Three sources, negations or two carries: the sum builds up in R253 (.CC) with the carry + // count in R254 (.X). Pu/Pv (bits 81/84) are set when the count reaches 1/2, and IADD3.X + // adds both carry-ins (bits 87, 77). A negated source is ~x, plus 1 unless extended. + const bool extended = insn.Bits(74, 1) != 0; + const bool negate_a = insn.Bits(72, 1), negate_b = opcode != 0x810 && insn.Bits(63, 1), negate_c = insn.Bits(75, 1); + if ((extended || insn.Bits(81, 6) != 0x3f) && + (insn.Bits(64, 8) != 255 || insn.Bits(84, 3) != 7 || negate_a || negate_b || negate_c || + (extended && insn.Bits(77, 4) != 15))) { + const u32 first_output = insn.Bits(81, 3), second_output = insn.Bits(84, 3); + if (rewritten_uniform || guarded_uniform || insn.Bits(73, 1) || insn.Bits(76, 1) || + (first_output != 7 && first_output == second_output) || + (!extended && (insn.Bits(77, 4) != 15 || insn.Bits(87, 4) != 15)) || + (opcode == 0x210 && (insn.words[1] & ~0x800000ffU)) || (opcode == 0xa10 && insn.Bits(62, 1)) || + (opcode == 0xc10 && (insn.words[1] & ~0x8000003fU))) + return fail(index, "native three-input carry mode"); + u32 a = insn.Bits(24, 8), b = operand(1, false), c = insn.Bits(64, 8); + const auto invert = [&](u32& source, u32 target) { + emit(0x0400000000000000ULL | predicate | target | (u64{source} << 8) | (u64{0xffffffffU} << 20) | (u64{2} << 53)); + source = target; + }; + if (negate_a) invert(a, 250); + if (negate_b) invert(b, 251); + if (negate_c) invert(c, 252); + const auto accumulate = [&](u32 source) { + emit(0x5c10000000000000ULL | predicate | 253 | (u64{253} << 8) | (u64{source} << 20) | (u64{1} << 47)); + emit(0x5c10000000000000ULL | predicate | 254 | (u64{254} << 8) | (u64{255} << 20) | (u64{1} << 43)); + }; + emit(0x5c10000000000000ULL | predicate | 253 | (u64{a} << 8) | (u64{b} << 20) | (u64{1} << 47)); + emit(0x5c10000000000000ULL | predicate | 254 | (u64{255} << 8) | (u64{255} << 20) | (u64{1} << 43)); + accumulate(c); + if (extended) { + for (const u32 carry : {insn.Bits(87, 4), insn.Bits(77, 4)}) { + if (carry == 15) continue; + mov_imm(250, 1); + if (carry != 7) + emit(0x5ca0000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{255} << 20) | + (u64{carry & 7} << 39) | (u64{carry >> 3} << 42)); + accumulate(250); + } + } else if (const u32 ones = u32{negate_a} + u32{negate_b} + u32{negate_c}) { + mov_imm(250, ones); + accumulate(250); + } + if (dest != 255) emit(base(0x5c98000000000000ULL) | (u64{253} << 20) | (u64{15} << 39)); + // An output that is also the guard predicate is written last. + const bool guard_first = (native_predicate & 7) != 7 && first_output == (native_predicate & 7); + for (const u32 output : {guard_first ? 1U : 0U, guard_first ? 0U : 1U}) { + const u32 target = output ? second_output : first_output; + if (target == 7) continue; + mov_imm(250, output + 1); + emit(0x5b60000000000000ULL | predicate | 7 | (u64{target} << 3) | (u64{254} << 8) | + (u64{250} << 20) | (u64{7} << 39) | (u64{6} << 49)); + } + break; + } + if ((opcode == 0xa10 || opcode == 0xc10) && insn.Bits(81, 3) != 7) { // cbuf or uniform b if (insn.Bits(64, 8) != 255 || insn.Bits(73, 4) || insn.Bits(84, 7) != 0x7f || - insn.Bits(77, 4) != 15 || insn.Bits(63, 1) || insn.Bits(72, 1)) + insn.Bits(77, 4) != 15 || insn.Bits(63, 1) || insn.Bits(72, 1) || + (opcode == 0xc10 && (insn.words[1] & ~0x3fU))) return fail(index, "native constant add carry mode"); const u32 b = operand(1, false); emit(base(0x5c10000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) | (u64{b} << 20)); @@ -2413,8 +2670,9 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32 (dest << 8) | (u64{b} << 20) | (u64{7} << 39) | (u64{1} << 49)); break; } - if (opcode == 0xa10 && insn.Bits(74, 1)) { - if ((insn.words[2] & ~0x07800000U) != 0x007fe4ffU) + if ((opcode == 0xa10 || opcode == 0xc10) && insn.Bits(74, 1)) { // IADD3.X Ra + cbuf or UR + carry predicate + if ((insn.words[2] & ~(0x07800000U | (opcode == 0xc10 ? 0x08000000U : 0U))) != 0x007fe4ffU || + (opcode == 0xc10 && (insn.words[1] & ~0x3fU))) return fail(index, "native extended constant add mode"); const u32 a = insn.Bits(24, 8), b = operand(1, false); mov_imm(250, 1); diff --git a/src/shader_recompiler/ir_opt/position_pass.cpp b/src/shader_recompiler/ir_opt/position_pass.cpp index 4890346944..600a596bf3 100644 --- a/src/shader_recompiler/ir_opt/position_pass.cpp +++ b/src/shader_recompiler/ir_opt/position_pass.cpp @@ -83,6 +83,37 @@ void BlitConstantsPass(Environment& env, IR::Program& program) { if (env.ShaderStage() != Stage::VertexB && env.ShaderStage() != Stage::Fragment) { return; } + // Bank 24 is root table 0, which games may load themselves (3D methods 0x504/0x508; the buffer + // cache binds it as bank 17): Fire Emblem: Fortune's Weave keeps its material push constants + // there. A shader reading bank 17 outside the blit block reads such a table and keeps its reads, + // as does one reading only words the blit block leaves zero (an unbound bank reads zero anyway; + // the game's enemy health gauges read only 0x40 of their table). + const auto in_blit_block = [](u32 offset) { + return offset < 0x20 || (offset >= 0x30 && offset < 0x4c) || (offset >= 0x50 && offset < 0x60); + }; + const auto is_nonzero_word = [](u32 offset) { + return offset < 0x20 || offset == 0x38 || offset == 0x3c || offset == 0x58 || offset == 0x5c; + }; + bool reads_nonzero_word{}; + for (IR::Block* const block : program.post_order_blocks) { + for (IR::Inst& inst : block->Instructions()) { + const IR::Opcode opcode{inst.GetOpcode()}; + if ((opcode != IR::Opcode::GetCbufU32 && opcode != IR::Opcode::GetCbufF32 && + opcode != IR::Opcode::GetCbufU32x2) || + !inst.Arg(0).IsImmediate() || inst.Arg(0).U32() != 17 || !inst.Arg(1).IsImmediate()) { + continue; + } + const u32 offset{inst.Arg(1).U32()}; + const bool pair{opcode == IR::Opcode::GetCbufU32x2}; + if (!in_blit_block(offset) || (pair && !in_blit_block(offset + 4))) { + return; + } + reads_nonzero_word |= is_nonzero_word(offset) || (pair && is_nonzero_word(offset + 4)); + } + } + if (!reads_nonzero_word) { + return; + } bool used{}; for (IR::Block* const block : program.post_order_blocks) { for (IR::Inst& inst : block->Instructions()) { |
