sharpemu/tests/SharpEmu.ShaderCompiler.Tests/Gen5WaveLaneLocalAnalysisTe...

263 lines
8.3 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.ShaderCompiler.Ir;
using Xunit;
using static SharpEmu.ShaderCompiler.Tests.Resources.ResourceTestProgram;
namespace SharpEmu.ShaderCompiler.Tests;
public sealed class Gen5WaveLaneLocalAnalysisTests
{
private const uint Exec = 126;
private static readonly Gen5Operand AllLanes = Gen5Operand.Source(193);
private readonly List<Gen5ShaderInstruction> _instructions = [];
private uint _pc;
private uint Add(Gen5ShaderInstruction instruction)
{
var pc = _pc;
_instructions.Add(instruction with { Pc = pc });
_pc += 8;
return pc;
}
private void Point(uint branchPc, uint targetPc)
{
var index = _instructions.FindIndex(instruction => instruction.Pc == branchPc);
var offset = (short)(((int)targetPc - (int)branchPc - 4) / 4);
_instructions[index] = _instructions[index] with { Words = [unchecked((uint)(ushort)offset)] };
}
private void Lane()
{
Add(Vop2(0, "VLshlrevB32", 2, Operand(3), Gen5Operand.Vector(1)));
Add(Vop2(0, "VAddI32", 2, Gen5Operand.Vector(0), Gen5Operand.Vector(2)));
}
private void CompareTo(string opcode, uint destination, Gen5Operand left, Gen5Operand right) =>
Add(new Gen5ShaderInstruction(0, Gen5ShaderEncoding.Vop3, opcode, [0u, 0u], [left, right], [Gen5Operand.Scalar(destination)],
new Gen5Vop3Control(0, 0, 0, false, 0, destination)));
private void SkipUnlessActive(Action body)
{
var skip = Add(Branch(0, "SCbranchExecz", 0));
body();
var join = Add(Sop1(0, "SMovB64", Exec, AllLanes));
Point(skip, join);
}
private bool Analyze(out string reason, params uint[] sharedFlatPcs)
{
Add(Vop2(0, "VLshlrevB32", 5, Operand(2), Gen5Operand.Vector(2)));
Add(BufferAccess(0, "BufferStoreDword", 4, vectorData: 4, offsetEnabled: true, vectorAddress: 5));
Add(EndProgram(0));
return Gen5WaveLaneLocalAnalysis.IsLaneLocal(Program([.. _instructions]), sharedFlatPcs.ToHashSet(), out reason);
}
private void AssertRefused(string expected)
{
Assert.False(Analyze(out var reason));
Assert.Contains(expected, reason, StringComparison.Ordinal);
}
[Fact]
public void LaneArithmetic_IsLaneLocal()
{
Lane();
Add(Vop2(0, "VAddU32", 4, Operand(100), Gen5Operand.Vector(2)));
Assert.True(Analyze(out _));
}
[Fact]
public void ACompareSelectingLanes_IsLaneLocal()
{
Lane();
CompareTo("VCmpLtU32", 10, Gen5Operand.Vector(2), Operand(40));
Add(Vop3(0, "VCndmaskB32", 4, Operand(0), Operand(1000), Gen5Operand.Scalar(10)));
Assert.True(Analyze(out _));
}
[Fact]
public void ReadingAnotherLane_IsRefused()
{
Lane();
Add(ReadLane(0, 20, 2, 45));
Add(MoveVectorFromScalar(0, 4, 20));
AssertRefused("V_READLANE");
}
[Fact]
public void CountingTheLanesOfAMask_IsRefused()
{
Lane();
Add(Sop1(0, "SBcnt1I32B64", 14, Gen5Operand.Scalar(Exec)));
Add(MoveVectorFromScalar(0, 4, 14));
AssertRefused("a lane mask used as a scalar value");
}
[Fact]
public void BroadcastingAMaskToTheLanes_IsRefused()
{
Lane();
CompareTo("VCmpLtU32", 10, Gen5Operand.Vector(2), Operand(40));
Add(MoveVectorFromScalar(0, 4, 10));
AssertRefused("a lane mask broadcast to the lanes");
}
[Fact]
public void SharedMemory_IsRefused()
{
Lane();
Add(DataShare(0, "DsWriteB32", false, [Gen5Operand.Vector(2), Gen5Operand.Vector(2)], []));
AssertRefused("LDS");
}
[Fact]
public void ExecFromALiteralMask_IsRefused()
{
Lane();
Add(Sop1(0, "SMovB64", Exec, Operand(0xFFFF)));
AssertRefused("EXEC set from the wave's layout");
}
[Fact]
public void AnExecSkipAroundLaneWork_IsLaneLocal()
{
Lane();
Add(Vopc(0, "VCmpxLeU32", Operand(40), 2));
SkipUnlessActive(() => Add(MoveVector(0, 4, 77)));
Assert.True(Analyze(out _));
}
[Fact]
public void ScalarStateWrittenInAnExecSkip_IsRefused()
{
Lane();
Add(MoveScalar(0, 20, 5));
Add(Vopc(0, "VCmpxLeU32", Operand(40), 2));
SkipUnlessActive(() => Add(MoveScalar(0, 20, 77)));
Add(MoveVectorFromScalar(0, 4, 20));
AssertRefused("scalar state escapes a lane-mask branch");
}
[Fact]
public void AMaskKeptThroughItsComplementInAnExecSkip_IsLaneLocal()
{
Lane();
Add(Sop1(0, "SMovB64", 2, Gen5Operand.Scalar(Exec)));
Add(Vopc(0, "VCmpxLeU32", Operand(40), 2));
var skip = Add(Branch(0, "SCbranchExecz", 0));
Add(Sop1(0, "SMovB64", 8, Gen5Operand.Scalar(Exec)));
Add(Vopc(0, "VCmpNeU32", Operand(0), 2));
Add(Sop2(0, "SOrn2B64", 106, Gen5Operand.Scalar(106), Gen5Operand.Scalar(8)));
Add(Sop2(0, "SAndB64", 2, Gen5Operand.Scalar(2), Gen5Operand.Scalar(106)));
var join = Add(Sop1(0, "SMovB64", Exec, Gen5Operand.Scalar(2)));
Point(skip, join);
Assert.True(Analyze(out var reason), reason);
}
[Fact]
public void AMaskChangedInAnExecSkip_IsRefused()
{
Lane();
Add(Sop1(0, "SMovB64", 2, Gen5Operand.Scalar(Exec)));
Add(Vopc(0, "VCmpxLeU32", Operand(40), 2));
var skip = Add(Branch(0, "SCbranchExecz", 0));
Add(Sop1(0, "SMovB64", 8, Gen5Operand.Scalar(Exec)));
Add(Sop2(0, "SOrB64", 2, Gen5Operand.Scalar(2), Gen5Operand.Scalar(8)));
Add(Sop1(0, "SNotB64", 2, Gen5Operand.Scalar(2)));
var join = Add(Sop1(0, "SMovB64", Exec, AllLanes));
Point(skip, join);
Add(Sop1(0, "SMovB64", Exec, Gen5Operand.Scalar(2)));
AssertRefused("scalar state escapes a lane-mask branch");
}
[Fact]
public void AWideMultiplyOfAScalarBesideAMask_IsLaneLocal()
{
Lane();
Add(Sop2(0, "SCselectB64", 10, Gen5Operand.Scalar(Exec), Operand(0)));
Add(Vop3(0, "VMadU64U32", 4, Gen5Operand.Scalar(9), Gen5Operand.Vector(2), Operand(0)));
Assert.True(Analyze(out var reason), reason);
}
[Fact]
public void ABarrierInAnExecSkip_IsRefused()
{
Lane();
Add(Vopc(0, "VCmpxLeU32", Operand(40), 2));
SkipUnlessActive(() => Add(new Gen5ShaderInstruction(0, Gen5ShaderEncoding.Sopp, "SBarrier", [0u], [], [], null)));
AssertRefused("a barrier");
}
[Fact]
public void ReadFirstLaneOfAUniformValue_IsLaneLocal()
{
Lane();
Add(MoveScalar(0, 20, 9));
Add(MoveVectorFromScalar(0, 3, 20));
Add(ReadFirstLane(0, 21, 3));
Add(MoveVectorFromScalar(0, 4, 21));
Assert.True(Analyze(out _));
}
[Fact]
public void ReadFirstLaneOfALaneValue_IsRefused()
{
Lane();
Add(ReadFirstLane(0, 21, 2));
Add(MoveVectorFromScalar(0, 4, 21));
AssertRefused("outside a waterfall loop");
}
[Fact]
public void AWaterfallLoop_IsLaneLocal()
{
Lane();
Add(Vop2(0, "VAndB32", 3, Operand(3), Gen5Operand.Vector(2)));
Add(Sop1(0, "SMovB64", 30, Gen5Operand.Scalar(Exec)));
var loop = Add(ReadFirstLane(0, 20, 3));
CompareTo("VCmpEqU32", 22, Gen5Operand.Vector(3), Gen5Operand.Scalar(20));
Add(Sop1(0, "SAndSaveexecB64", 24, Gen5Operand.Scalar(22)));
Add(MoveVectorFromScalar(0, 4, 20));
Add(Sop2(0, "SXorB64", Exec, Gen5Operand.Scalar(24), Gen5Operand.Scalar(Exec)));
var back = Add(Branch(0, "SCbranchExecnz", 0));
Point(back, loop);
Add(Sop1(0, "SMovB64", Exec, Gen5Operand.Scalar(30)));
Assert.True(Analyze(out var reason), reason);
}
[Theory]
[InlineData(0x111u, true)]
[InlineData(0x141u, true)]
[InlineData(0x142u, false)]
[InlineData(0x143u, false)]
public void Dpp_IsLaneLocalOnlyWithinARow(uint control, bool laneLocal)
{
Lane();
Add(new Gen5ShaderInstruction(0, Gen5ShaderEncoding.Vop1, "VMovB32", [0u, 0u], [Gen5Operand.Vector(2)], [Gen5Operand.Vector(4)],
new Gen5DppControl(control, false, false, 0, 0, 0xF, 0xF)));
Assert.Equal(laneLocal, Analyze(out var reason));
Assert.True(laneLocal || reason.Contains("cross-row DPP", StringComparison.Ordinal));
}
}