mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 06:59:45 +08:00
444af50f4b
* [shader] add scalar control flow graph Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [shader] add scalar control flow graph * [shader] add gen5 branch resolver Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [shader] add gen5 branch resolver * [shader] add scalar reaching definitions Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [shader] add scalar reaching definitions * [shader] add cfg and dataflow tests Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [shader] add cfg and dataflow tests * [shader] guard descriptors from unresolved registers Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [shader] guard descriptors from unresolved registers --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
143 lines
4.3 KiB
C#
143 lines
4.3 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using System.Collections.Generic;
|
|
using SharpEmu.ShaderCompiler;
|
|
using SharpEmu.ShaderCompiler.Ir;
|
|
using Xunit;
|
|
|
|
namespace SharpEmu.ShaderCompiler.Tests;
|
|
|
|
public sealed class Gen5ReachingDefinitionTests
|
|
{
|
|
private static Gen5ShaderInstruction Load(uint pc, uint destination) =>
|
|
new(
|
|
pc,
|
|
Gen5ShaderEncoding.Smem,
|
|
"SLoadDwordx4",
|
|
[0u, 0u],
|
|
[Gen5Operand.Scalar(0)],
|
|
[Gen5Operand.Scalar(destination)],
|
|
null);
|
|
|
|
private static Gen5ShaderInstruction Nop(uint pc) =>
|
|
new(pc, Gen5ShaderEncoding.Sop1, "SNop", [0u], [], [], null);
|
|
|
|
private static Gen5ShaderInstruction Branch(uint pc, string opcode, short wordOffset) =>
|
|
new(
|
|
pc,
|
|
Gen5ShaderEncoding.Sopp,
|
|
opcode,
|
|
[unchecked((uint)(ushort)wordOffset)],
|
|
[],
|
|
[],
|
|
null);
|
|
|
|
[Fact]
|
|
public void SingleDefinitionIsTracked()
|
|
{
|
|
List<Gen5ShaderInstruction> program = [Load(0, 16), Nop(4)];
|
|
|
|
var ssa = Gen5ScalarSsa.Build(program, userData: []);
|
|
var reaching = ssa.GetReachingDefinitionAt(4, 16);
|
|
|
|
Assert.Equal(IrReachingState.Single, reaching.State);
|
|
Assert.Equal(0u, reaching.DefinitionPc);
|
|
}
|
|
|
|
[Fact]
|
|
public void TwoDefinitionsOnDifferentPathsBecomeMultiple()
|
|
{
|
|
// The case that produced garbage descriptors: the same register is
|
|
// written on both sides of a branch and read after the join.
|
|
List<Gen5ShaderInstruction> program =
|
|
[
|
|
Branch(0, "SCbranchScc0", 2),
|
|
Load(4, 16),
|
|
Branch(8, "SBranch", 1),
|
|
Load(12, 16),
|
|
Nop(16),
|
|
];
|
|
|
|
var ssa = Gen5ScalarSsa.Build(program, userData: []);
|
|
|
|
Assert.Equal(IrReachingState.Multiple, ssa.GetReachingDefinitionAt(16, 16).State);
|
|
}
|
|
|
|
[Fact]
|
|
public void DefinitionOnOnlyOnePathIsAlsoMultipleAtTheJoin()
|
|
{
|
|
List<Gen5ShaderInstruction> program =
|
|
[
|
|
Load(0, 16),
|
|
Branch(4, "SCbranchScc0", 1),
|
|
Load(8, 16),
|
|
Nop(12),
|
|
];
|
|
|
|
var ssa = Gen5ScalarSsa.Build(program, userData: []);
|
|
|
|
Assert.Equal(IrReachingState.Multiple, ssa.GetReachingDefinitionAt(12, 16).State);
|
|
}
|
|
|
|
[Fact]
|
|
public void DefinitionBeforeTheBranchStaysSingle()
|
|
{
|
|
List<Gen5ShaderInstruction> program =
|
|
[
|
|
Load(0, 16),
|
|
Branch(4, "SCbranchScc0", 1),
|
|
Load(8, 16),
|
|
Nop(12),
|
|
];
|
|
|
|
var ssa = Gen5ScalarSsa.Build(program, userData: []);
|
|
var reaching = ssa.GetReachingDefinitionAt(4, 16);
|
|
|
|
Assert.Equal(IrReachingState.Single, reaching.State);
|
|
Assert.Equal(0u, reaching.DefinitionPc);
|
|
}
|
|
|
|
[Fact]
|
|
public void UserDataCountsAsADefinition()
|
|
{
|
|
List<Gen5ShaderInstruction> program = [Nop(0)];
|
|
|
|
var ssa = Gen5ScalarSsa.Build(program, userData: [0x1111, 0x2222]);
|
|
|
|
Assert.Equal(IrReachingState.Single, ssa.GetReachingDefinitionAt(0, 0).State);
|
|
Assert.Equal(IrReachingState.None, ssa.GetReachingDefinitionAt(0, 64).State);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnwrittenRegisterHasNoDefinition()
|
|
{
|
|
List<Gen5ShaderInstruction> program = [Load(0, 16), Nop(4)];
|
|
|
|
var ssa = Gen5ScalarSsa.Build(program, userData: []);
|
|
|
|
Assert.Equal(IrReachingState.None, ssa.GetReachingDefinitionAt(4, 32).State);
|
|
}
|
|
|
|
[Fact]
|
|
public void ReachingDefinitionSeesLoadsThatConstantPropagationCannot()
|
|
{
|
|
// The whole point of the second analysis: SLoadDwordx4 has no compile-time
|
|
// value, so constant propagation reports Unknown and never Merged. Reaching
|
|
// definitions still proves the register has two possible writers.
|
|
List<Gen5ShaderInstruction> program =
|
|
[
|
|
Branch(0, "SCbranchScc0", 2),
|
|
Load(4, 16),
|
|
Branch(8, "SBranch", 1),
|
|
Load(12, 16),
|
|
Nop(16),
|
|
];
|
|
|
|
var ssa = Gen5ScalarSsa.Build(program, userData: []);
|
|
|
|
Assert.Equal(IrScalarState.Unknown, ssa.GetScalarAt(16, 16).State);
|
|
Assert.Equal(IrReachingState.Multiple, ssa.GetReachingDefinitionAt(16, 16).State);
|
|
}
|
|
}
|