[ShaderCompiler] Analyze wave64 half masks and shared-memory hazards

This commit is contained in:
ParantezTech 2026-10-01 17:18:59 +03:00
parent b9a8aafee2
commit d36420515b
2 changed files with 786 additions and 0 deletions

View File

@ -0,0 +1,565 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.ShaderCompiler.Ir;
public sealed class Gen5Wave64HalfMaskPlan
{
public required IReadOnlyDictionary<uint, uint[]> ExactPairsBefore { get; init; }
public required IReadOnlyDictionary<uint, uint> HalfMaskWrites { get; init; }
public required IReadOnlySet<uint> SharedMemoryBarriersBefore { get; init; }
}
public static class Gen5Wave64HalfMaskAnalysis
{
private const uint Vcc = 106;
private const uint Exec = 126;
private const uint VcczSource = 251;
private const uint ExeczSource = 252;
private const uint SccSource = 253;
private static readonly UInt128 EvenRegisters = new(0x5555_5555_5555_5555UL, 0x5555_5555_5555_5555UL);
private static readonly UInt128 OddRegisters = EvenRegisters << 1;
private static readonly HashSet<string> CarryIn = new(StringComparer.Ordinal)
{
"VAddcU32", "VAddCoCiU32", "VSubbU32", "VSubCoCiU32", "VSubbrevU32", "VSubrevCoCiU32",
};
private static readonly HashSet<string> CarryOut = new(CarryIn, StringComparer.Ordinal)
{
"VAddCoU32", "VSubCoU32", "VSubrevCoU32", "VMadU64U32", "VMadI64I32",
};
private static readonly HashSet<string> BitwiseWithScc = new(StringComparer.Ordinal)
{
"SNotB64", "SAndB64", "SOrB64", "SXorB64", "SAndn1B64", "SAndn2B64", "SOrn1B64", "SOrn2B64",
"SNandB64", "SNorB64", "SXnorB64",
"SAndSaveexecB64", "SOrSaveexecB64", "SXorSaveexecB64", "SAndn1SaveexecB64", "SAndn2SaveexecB64",
"SOrn1SaveexecB64", "SOrn2SaveexecB64", "SNandSaveexecB64", "SNorSaveexecB64", "SXnorSaveexecB64",
};
private static readonly HashSet<string> BitwiseWithoutScc = new(StringComparer.Ordinal)
{
"SMovB64", "SWqmB64", "SCselectB64",
};
private static readonly HashSet<string> SccReaders = new(StringComparer.Ordinal)
{
"SCbranchScc0", "SCbranchScc1", "SAddcU32", "SSubbU32", "SCselectB32", "SCselectB64",
};
private static readonly HashSet<string> SccWriters = new(StringComparer.Ordinal)
{
"SNotB32", "SAbsI32", "SWqmB32", "SBrevB32", "SBcnt1I32B32", "SBcnt1I32B64",
"SAddU32", "SSubU32", "SAddI32", "SSubI32", "SAddcU32", "SSubbU32",
"SAndB32", "SOrB32", "SXorB32", "SAndn2B32", "SOrn2B32", "SNandB32", "SNorB32", "SXnorB32",
"SLshlB32", "SLshrB32", "SAshrI32", "SBfeU32", "SBfeI32", "SMinU32", "SMinI32", "SMaxU32", "SMaxI32",
"SLshlB64", "SLshrB64", "SAshrI64", "SBfeU64", "SBfeI64", "SBfmB64",
};
private static readonly HashSet<string> DynamicControlFlow = new(StringComparer.Ordinal)
{
"SSetpcB64", "SSwappcB64", "SRfeB64", "SCbranchJoin", "SCbranchGFork", "SCbranchIFork",
};
private readonly record struct State(bool Reached, UInt128 Half, bool PendingRead, bool PendingWrite)
{
public static State Meet(State left, State right) =>
!left.Reached ? right
: !right.Reached ? left
: new State(true, left.Half | right.Half, left.PendingRead | right.PendingRead, left.PendingWrite | right.PendingWrite);
}
public static Gen5Wave64HalfMaskPlan? Analyze(Gen5ShaderProgram program, IReadOnlySet<uint> sharedFlatPcs)
{
var instructions = program.Instructions;
if (instructions.Count == 0)
{
return null;
}
var successors = BuildSuccessors(instructions);
if (successors is null)
{
return null;
}
var sccLiveAfter = ComputeSccLiveAfter(instructions, successors);
var states = new State[instructions.Count];
states[0] = new State(true, 0, false, false);
var pending = new Queue<int>();
var queued = new bool[instructions.Count];
pending.Enqueue(0);
queued[0] = true;
while (pending.TryDequeue(out var index))
{
queued[index] = false;
var output = Step(instructions[index], states[index], sccLiveAfter[index], sharedFlatPcs, out _, out _, out _);
foreach (var successor in successors[index])
{
var merged = State.Meet(states[successor], output);
if (merged != states[successor])
{
states[successor] = merged;
if (!queued[successor])
{
queued[successor] = true;
pending.Enqueue(successor);
}
}
}
}
var exactPairs = new Dictionary<uint, uint[]>();
var halfWrites = new Dictionary<uint, uint>();
var barriers = new HashSet<uint>();
for (var index = 0; index < instructions.Count; index++)
{
if (!states[index].Reached)
{
continue;
}
var instruction = instructions[index];
Step(instruction, states[index], sccLiveAfter[index], sharedFlatPcs, out var exact, out var barrier, out var halfWrite);
if (exact != 0)
{
exactPairs[instruction.Pc] = PairBases(exact);
}
if (barrier)
{
barriers.Add(instruction.Pc);
}
if (halfWrite && TryGetHalfMaskDestination(instruction, out var maskDestination))
{
halfWrites[instruction.Pc] = maskDestination;
}
}
return new Gen5Wave64HalfMaskPlan
{
ExactPairsBefore = exactPairs,
HalfMaskWrites = halfWrites,
SharedMemoryBarriersBefore = barriers,
};
}
private static State Step(
Gen5ShaderInstruction instruction,
State input,
bool sccLiveAfter,
IReadOnlySet<uint> sharedFlatPcs,
out UInt128 exact,
out bool sharedBarrier,
out bool halfWrite)
{
var opcode = instruction.Opcode;
var half = input.Half;
var bitwise = IsBitwiseMaskOperation(instruction);
UInt128 needed = 0;
UInt128 bitwiseInputs = 0;
for (var index = 0; index < instruction.Sources.Count; index++)
{
var operand = instruction.Sources[index];
if (operand.Kind == Gen5OperandKind.EncodedConstant)
{
if (operand.Value == VcczSource)
{
needed |= Pair(Vcc);
}
else if (operand.Value == ExeczSource)
{
needed |= Pair(Exec);
}
continue;
}
if (operand.Kind != Gen5OperandKind.ScalarRegister)
{
continue;
}
if (bitwise)
{
bitwiseInputs |= Pair(operand.Value);
continue;
}
if (index == 2 && (opcode == "VCndmaskB32" || CarryIn.Contains(opcode)))
{
continue;
}
needed |= ReadRegisters(instruction, index, operand.Value);
}
if (opcode is "SCbranchExecz" or "SCbranchExecnz")
{
needed |= Pair(Exec);
}
else if (opcode is "SCbranchVccz" or "SCbranchVccnz")
{
needed |= Pair(Vcc);
}
if ((instruction.Encoding == Gen5ShaderEncoding.Sopk || opcode.StartsWith("SBitset", StringComparison.Ordinal)) &&
instruction.Destinations is [{ Kind: Gen5OperandKind.ScalarRegister } readDestination, ..])
{
needed |= Range(readDestination.Value, opcode.Contains("64", StringComparison.Ordinal) ? 2u : 1u);
}
if (opcode.StartsWith("SMovrel", StringComparison.Ordinal))
{
needed |= half;
}
if (bitwise)
{
if (opcode.EndsWith("SaveexecB64", StringComparison.Ordinal))
{
bitwiseInputs |= Pair(Exec);
}
if (sccLiveAfter && BitwiseWithScc.Contains(opcode))
{
needed |= bitwiseInputs;
}
}
exact = Expand(needed & half);
half &= ~exact;
var pendingRead = input.PendingRead;
var pendingWrite = input.PendingWrite;
if (exact != 0)
{
pendingRead = false;
pendingWrite = false;
}
var (reads, writes) = SharedMemoryAccess(instruction, sharedFlatPcs);
sharedBarrier = (writes && (pendingRead || pendingWrite)) || (reads && pendingWrite);
if (sharedBarrier)
{
pendingRead = false;
pendingWrite = false;
}
pendingRead |= reads;
pendingWrite |= writes;
if (opcode == "SBarrier")
{
pendingRead = false;
pendingWrite = false;
}
halfWrite = false;
if (TryGetHalfMaskDestination(instruction, out var maskDestination))
{
half |= Pair(maskDestination);
halfWrite = true;
}
else if (bitwise)
{
var destination = instruction.Destinations[0].Value;
var outputHalf = (bitwiseInputs & half) != 0;
half = outputHalf ? half | Pair(destination) : half & ~Pair(destination);
if (opcode.EndsWith("SaveexecB64", StringComparison.Ordinal))
{
half = outputHalf ? half | Pair(Exec) : half & ~Pair(Exec);
}
}
else
{
var wide = IsWideScalar(opcode);
foreach (var destination in instruction.Destinations)
{
if (destination.Kind != Gen5OperandKind.ScalarRegister)
{
continue;
}
half &= ~Bit(destination.Value);
if (wide)
{
half &= ~Bit(destination.Value + 1);
}
}
if (opcode.EndsWith("SaveexecB32", StringComparison.Ordinal))
{
half &= ~Pair(Exec);
}
}
return new State(true, half, pendingRead, pendingWrite);
}
private static bool TryGetHalfMaskDestination(Gen5ShaderInstruction instruction, out uint destination)
{
var opcode = instruction.Opcode;
if (opcode.StartsWith("VCmpx", StringComparison.Ordinal))
{
destination = Exec;
}
else if (opcode.StartsWith("VCmp", StringComparison.Ordinal))
{
destination = instruction.Control switch
{
Gen5SdwaControl { ScalarDestination: { } sdwa } => sdwa,
Gen5Vop3Control { ScalarDestination: { } vop3 } => vop3,
_ => Vcc,
};
}
else if (CarryOut.Contains(opcode))
{
destination = instruction.Control is Gen5Vop3Control { ScalarDestination: { } carry } ? carry : Vcc;
}
else
{
destination = 0;
return false;
}
return (destination & 1) == 0 && (destination < 124 || destination == Exec);
}
private static bool IsBitwiseMaskOperation(Gen5ShaderInstruction instruction)
{
if (!BitwiseWithScc.Contains(instruction.Opcode) && !BitwiseWithoutScc.Contains(instruction.Opcode))
{
return false;
}
if (instruction.Destinations is not [{ Kind: Gen5OperandKind.ScalarRegister } destination] ||
(destination.Value & 1) != 0 ||
(destination.Value >= 124 && destination.Value != Exec))
{
return false;
}
foreach (var source in instruction.Sources)
{
if (source.Kind == Gen5OperandKind.VectorRegister)
{
return false;
}
if (source.Kind == Gen5OperandKind.ScalarRegister &&
((source.Value & 1) != 0 || (source.Value >= 124 && source.Value != Exec)))
{
return false;
}
}
return true;
}
private static (bool Reads, bool Writes) SharedMemoryAccess(Gen5ShaderInstruction instruction, IReadOnlySet<uint> sharedFlatPcs)
{
if (instruction.Control is Gen5DataShareControl { Gds: false })
{
var opcode = instruction.Opcode;
if (opcode is "DsSwizzleB32" or "DsBpermuteB32" or "DsPermuteB32" or "DsNop")
{
return (false, false);
}
if (opcode.StartsWith("DsRead", StringComparison.Ordinal))
{
return (true, false);
}
if (opcode.StartsWith("DsWrite", StringComparison.Ordinal))
{
return (false, true);
}
return (true, true);
}
return sharedFlatPcs.Contains(instruction.Pc) ? (true, true) : (false, false);
}
private static int[][]? BuildSuccessors(IReadOnlyList<Gen5ShaderInstruction> instructions)
{
var indexByPc = new Dictionary<uint, int>(instructions.Count);
for (var index = 0; index < instructions.Count; index++)
{
if (DynamicControlFlow.Contains(instructions[index].Opcode))
{
return null;
}
indexByPc[instructions[index].Pc] = index;
}
var last = instructions[^1];
var endPc = last.Pc + (uint)(last.Words.Count * sizeof(uint));
var successors = new int[instructions.Count][];
for (var index = 0; index < instructions.Count; index++)
{
var instruction = instructions[index];
var next = index + 1 < instructions.Count ? index + 1 : -1;
if (Gen5IrBranchResolver.IsTerminator(instruction))
{
successors[index] = [];
continue;
}
var unconditional = Gen5IrBranchResolver.IsUnconditionalBranch(instruction);
if (!unconditional && !Gen5IrBranchResolver.Instance.IsConditional(instruction))
{
successors[index] = next < 0 ? [] : [next];
continue;
}
if (!Gen5IrBranchResolver.Instance.TryGetBranchTarget(instruction, out var targetPc))
{
return null;
}
var targets = new List<int>(2);
if (indexByPc.TryGetValue(targetPc, out var target))
{
targets.Add(target);
}
else if (targetPc < endPc)
{
return null;
}
if (!unconditional && next >= 0)
{
targets.Add(next);
}
successors[index] = [.. targets];
}
return successors;
}
private static bool[] ComputeSccLiveAfter(IReadOnlyList<Gen5ShaderInstruction> instructions, int[][] successors)
{
var liveBefore = new bool[instructions.Count];
var liveAfter = new bool[instructions.Count];
bool changed;
do
{
changed = false;
for (var index = instructions.Count - 1; index >= 0; index--)
{
var after = false;
foreach (var successor in successors[index])
{
after |= liveBefore[successor];
}
var instruction = instructions[index];
var before = ReadsScc(instruction) || (!WritesScc(instruction) && after);
if (after != liveAfter[index] || before != liveBefore[index])
{
liveAfter[index] = after;
liveBefore[index] = before;
changed = true;
}
}
}
while (changed);
return liveAfter;
}
private static bool ReadsScc(Gen5ShaderInstruction instruction)
{
if (SccReaders.Contains(instruction.Opcode))
{
return true;
}
foreach (var source in instruction.Sources)
{
if (source.Kind == Gen5OperandKind.EncodedConstant && source.Value == SccSource)
{
return true;
}
}
return false;
}
private static bool WritesScc(Gen5ShaderInstruction instruction) =>
instruction.Encoding == Gen5ShaderEncoding.Sopc ||
instruction.Opcode.StartsWith("SCmpk", StringComparison.Ordinal) ||
instruction.Opcode.EndsWith("SaveexecB32", StringComparison.Ordinal) ||
SccWriters.Contains(instruction.Opcode) ||
BitwiseWithScc.Contains(instruction.Opcode);
private static bool IsWideScalar(string opcode) =>
opcode.StartsWith('S') &&
(opcode.EndsWith("B64", StringComparison.Ordinal) ||
opcode.EndsWith("U64", StringComparison.Ordinal) ||
opcode.EndsWith("I64", StringComparison.Ordinal));
private static UInt128 ReadRegisters(Gen5ShaderInstruction instruction, int sourceIndex, uint register)
{
var scalarOrdinal = 0;
for (var index = 0; index < sourceIndex; index++)
{
if (instruction.Sources[index].Kind == Gen5OperandKind.ScalarRegister)
{
scalarOrdinal++;
}
}
var count = instruction.Encoding switch
{
Gen5ShaderEncoding.Smem or Gen5ShaderEncoding.Smrd => scalarOrdinal != 0 ? 1u
: instruction.Opcode.StartsWith("SLoad", StringComparison.Ordinal) ? 2u : 4u,
Gen5ShaderEncoding.Mubuf or Gen5ShaderEncoding.Mtbuf => scalarOrdinal == 0 ? 4u : 1u,
Gen5ShaderEncoding.Mimg => scalarOrdinal == 0 ? 8u : 4u,
Gen5ShaderEncoding.Flat => 2u,
_ => instruction.Opcode.Contains("64", StringComparison.Ordinal) ? 2u : 1u,
};
return Range(register, count);
}
private static UInt128 Range(uint register, uint count)
{
UInt128 registers = 0;
for (var offset = 0u; offset < count; offset++)
{
registers |= Bit(register + offset);
}
return registers;
}
private static UInt128 Expand(UInt128 registers) =>
registers | ((registers & EvenRegisters) << 1) | ((registers & OddRegisters) >> 1);
private static uint[] PairBases(UInt128 registers)
{
var bases = new List<uint>();
for (uint register = 0; register < 128; register += 2)
{
if ((registers & Pair(register)) != 0)
{
bases.Add(register);
}
}
return [.. bases];
}
private static UInt128 Pair(uint register) => Expand(Bit(register));
private static UInt128 Bit(uint register) => register < 128 ? UInt128.One << (int)register : 0;
}

View File

@ -0,0 +1,221 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.ShaderCompiler;
using SharpEmu.ShaderCompiler.Ir;
using Xunit;
namespace SharpEmu.ShaderCompiler.Tests;
public sealed class Gen5Wave64HalfMaskAnalysisTests
{
private const uint Vcc = 106;
private const uint Exec = 126;
private readonly List<Gen5ShaderInstruction> _program = [];
private uint _pc;
private uint Add(string opcode, Gen5ShaderEncoding encoding, Gen5Operand[] destinations, Gen5Operand[] sources,
Gen5InstructionControl? control = null, uint word = 0)
{
var pc = _pc;
_program.Add(new Gen5ShaderInstruction(pc, encoding, opcode, [word], sources, destinations, control));
_pc += 4;
return pc;
}
private uint CompareToVcc() =>
Add("VCmpLtU32", Gen5ShaderEncoding.Vopc, [], [Gen5Operand.Vector(0), Gen5Operand.Vector(1)]);
private uint CompareTo(uint destination) =>
Add("VCmpLtU32", Gen5ShaderEncoding.Vop3, [Gen5Operand.Scalar(destination)], [Gen5Operand.Vector(0), Gen5Operand.Vector(1)],
new Gen5Vop3Control(0, 0, 0, false, 0, destination));
private uint CompareToExec() =>
Add("VCmpxLtU32", Gen5ShaderEncoding.Vopc, [], [Gen5Operand.Vector(0), Gen5Operand.Vector(1)]);
private uint Select(uint mask) =>
Add("VCndmaskB32", Gen5ShaderEncoding.Vop3, [Gen5Operand.Vector(2)], [Gen5Operand.Vector(3), Gen5Operand.Vector(4), Gen5Operand.Scalar(mask)],
new Gen5Vop3Control(0, 0, 0, false, 0, null));
private uint Scalar1(string opcode, uint destination, params Gen5Operand[] sources) =>
Add(opcode, Gen5ShaderEncoding.Sop1, [Gen5Operand.Scalar(destination)], sources);
private uint Scalar2(string opcode, uint destination, params Gen5Operand[] sources) =>
Add(opcode, Gen5ShaderEncoding.Sop2, [Gen5Operand.Scalar(destination)], sources);
private uint Branch(string opcode) => Add(opcode, Gen5ShaderEncoding.Sopp, [], [], word: 0xBF840000u);
private uint DataShare(string opcode) =>
Add(opcode, Gen5ShaderEncoding.Ds, opcode.StartsWith("DsRead", StringComparison.Ordinal) ? [Gen5Operand.Vector(5)] : [],
[Gen5Operand.Vector(0), Gen5Operand.Vector(1)], new Gen5DataShareControl(0, 0, false));
private uint End() => Add("SEndpgm", Gen5ShaderEncoding.Sopp, [], [], word: 0xBF810000u);
private void PointBranch(uint branchPc, uint targetPc)
{
var index = _program.FindIndex(instruction => instruction.Pc == branchPc);
var original = _program[index];
var offset = (ushort)(short)((int)(targetPc - branchPc - 4) / 4);
_program[index] = original with { Words = [(original.Words[0] & 0xFFFF0000u) | offset] };
}
private Gen5Wave64HalfMaskPlan Analyze() =>
Gen5Wave64HalfMaskAnalysis.Analyze(new Gen5ShaderProgram(0x1000, _program), new HashSet<uint>())
?? throw new InvalidOperationException("analysis gave up");
[Fact]
public void ACompareReadPerLane_NeedsNoWholeWaveMask()
{
var compare = CompareTo(20);
var select = Select(20);
End();
var plan = Analyze();
Assert.Equal(20u, plan.HalfMaskWrites[compare]);
Assert.False(plan.ExactPairsBefore.ContainsKey(select));
}
[Fact]
public void ABranchOnVcc_CombinesVccFirst()
{
CompareToVcc();
var branch = Branch("SCbranchVccz");
End();
PointBranch(branch, _pc - 4);
Assert.Equal(new[] { Vcc }, Analyze().ExactPairsBefore[branch]);
}
[Fact]
public void ABranchOnExec_CombinesExecFirst()
{
CompareToExec();
var branch = Branch("SCbranchExecz");
End();
PointBranch(branch, _pc - 4);
Assert.Equal(new[] { Exec }, Analyze().ExactPairsBefore[branch]);
}
[Fact]
public void ABitwiseMaskOperationWithADeadScc_StaysHalf()
{
CompareTo(20);
CompareToVcc();
var and = Scalar2("SAndB64", 22, Gen5Operand.Scalar(20), Gen5Operand.Scalar(Vcc));
var select = Select(22);
End();
var plan = Analyze();
Assert.False(plan.ExactPairsBefore.ContainsKey(and));
Assert.False(plan.ExactPairsBefore.ContainsKey(select));
}
[Fact]
public void ABitwiseMaskOperationWhoseSccIsRead_CombinesItsInputs()
{
CompareTo(20);
CompareToVcc();
var and = Scalar2("SAndB64", 22, Gen5Operand.Scalar(20), Gen5Operand.Scalar(Vcc));
var branch = Branch("SCbranchScc0");
End();
PointBranch(branch, _pc - 4);
var plan = Analyze();
Assert.Equal(new[] { 20u, Vcc }, plan.ExactPairsBefore[and]);
Assert.False(plan.ExactPairsBefore.ContainsKey(branch));
}
[Fact]
public void AScalarReadOfAMaskHalf_CombinesThePair()
{
CompareTo(20);
var move = Scalar1("SMovB32", 30, Gen5Operand.Scalar(21));
End();
Assert.Equal(new[] { 20u }, Analyze().ExactPairsBefore[move]);
}
[Fact]
public void ACombinedMask_IsNotCombinedAgain()
{
CompareTo(20);
var first = Scalar1("SMovB32", 30, Gen5Operand.Scalar(20));
var second = Scalar1("SMovB32", 31, Gen5Operand.Scalar(21));
End();
var plan = Analyze();
Assert.Equal(new[] { 20u }, plan.ExactPairsBefore[first]);
Assert.False(plan.ExactPairsBefore.ContainsKey(second));
}
[Fact]
public void ASaveexecCopiesAHalfExec_SoTheRestoreStaysHalf()
{
CompareTo(20);
var save = Scalar1("SAndSaveexecB64", 24, Gen5Operand.Scalar(20));
Select(Exec);
var restore = Scalar2("SOrB64", Exec, Gen5Operand.Scalar(Exec), Gen5Operand.Scalar(24));
var read = Scalar1("SMovB32", 30, Gen5Operand.Scalar(24));
End();
var plan = Analyze();
Assert.False(plan.ExactPairsBefore.ContainsKey(save));
Assert.False(plan.ExactPairsBefore.ContainsKey(restore));
Assert.Equal(new[] { 24u }, plan.ExactPairsBefore[read]);
}
[Fact]
public void ALoopExitOnExec_CombinesExecEveryIteration()
{
var header = CompareToExec();
var exit = Branch("SCbranchExecz");
Select(Exec);
var back = Branch("SBranch");
var end = End();
PointBranch(exit, end);
PointBranch(back, header);
var plan = Analyze();
Assert.Equal(new[] { Exec }, plan.ExactPairsBefore[exit]);
Assert.Contains(header, plan.HalfMaskWrites.Keys);
}
[Fact]
public void ASharedReadAfterAWrite_WaitsForTheOtherHalf()
{
var write = DataShare("DsWriteB32");
var read = DataShare("DsReadB32");
var again = DataShare("DsReadB32");
End();
var plan = Analyze();
Assert.DoesNotContain(write, plan.SharedMemoryBarriersBefore);
Assert.Contains(read, plan.SharedMemoryBarriersBefore);
Assert.DoesNotContain(again, plan.SharedMemoryBarriersBefore);
}
[Fact]
public void ACombineBarrier_AlsoOrdersSharedMemory()
{
DataShare("DsWriteB32");
CompareTo(20);
var move = Scalar1("SMovB32", 30, Gen5Operand.Scalar(20));
var read = DataShare("DsReadB32");
End();
var plan = Analyze();
Assert.True(plan.ExactPairsBefore.ContainsKey(move));
Assert.DoesNotContain(read, plan.SharedMemoryBarriersBefore);
}
[Fact]
public void ADynamicJump_GivesUp()
{
Scalar1("SSetpcB64", 0, Gen5Operand.Scalar(4));
End();
Assert.Null(Gen5Wave64HalfMaskAnalysis.Analyze(new Gen5ShaderProgram(0x1000, _program), new HashSet<uint>()));
}
}