// 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 Gen5ScalarSsaTests { private static Gen5ShaderInstruction Mov(uint pc, uint destination, uint literal) => new( pc, Gen5ShaderEncoding.Sop1, "SMov", [0u], [new Gen5Operand(Gen5OperandKind.LiteralConstant, literal)], [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 StraightLineMovResolvesToAConstant() { List program = [ Mov(0, destination: 8, literal: 0x1234), Nop(4), ]; var ssa = Gen5ScalarSsa.Build(program, userData: []); var value = ssa.GetScalarAt(4, 8); Assert.Equal(IrScalarState.Constant, value.State); Assert.Equal(0x1234u, value.Constant); } [Fact] public void UserDataSeedsTheEntryState() { List program = [Nop(0)]; var ssa = Gen5ScalarSsa.Build(program, userData: [0x40F55240, 0x00000004]); Assert.Equal(IrScalarState.Constant, ssa.GetScalarAt(0, 0).State); Assert.Equal(0x40F55240u, ssa.GetScalarAt(0, 0).Constant); Assert.Equal(0x00000004u, ssa.GetScalarAt(0, 1).Constant); } [Fact] public void ConflictingValuesFromTwoPathsBecomeMerged() { // if (cc) s8 = 0xAAAA else s8 = 0xBBBB; use s8 List program = [ Branch(0, "SCbranchScc0", 2), Mov(4, destination: 8, literal: 0xAAAA), Branch(8, "SBranch", 1), Mov(12, destination: 8, literal: 0xBBBB), Nop(16), ]; var ssa = Gen5ScalarSsa.Build(program, userData: []); Assert.True(ssa.Graph.HasControlFlow); Assert.Equal(IrScalarState.Merged, ssa.GetScalarAt(16, 8).State); } [Fact] public void SameValueOnBothPathsStaysResolved() { List program = [ Branch(0, "SCbranchScc0", 2), Mov(4, destination: 8, literal: 0xCAFE), Branch(8, "SBranch", 1), Mov(12, destination: 8, literal: 0xCAFE), Nop(16), ]; var ssa = Gen5ScalarSsa.Build(program, userData: []); var value = ssa.GetScalarAt(16, 8); Assert.Equal(IrScalarState.Constant, value.State); Assert.Equal(0xCAFEu, value.Constant); } [Fact] public void RegisterWrittenOnOnlyOnePathIsMergedAtTheJoin() { List program = [ Mov(0, destination: 8, literal: 0x1111), Branch(4, "SCbranchScc0", 1), Mov(8, destination: 8, literal: 0x2222), Nop(12), ]; var ssa = Gen5ScalarSsa.Build(program, userData: []); Assert.Equal(IrScalarState.Merged, ssa.GetScalarAt(12, 8).State); } [Fact] public void ValueBeforeTheBranchIsStillResolved() { List program = [ Mov(0, destination: 8, literal: 0x1111), Branch(4, "SCbranchScc0", 1), Mov(8, destination: 8, literal: 0x2222), Nop(12), ]; var ssa = Gen5ScalarSsa.Build(program, userData: []); var value = ssa.GetScalarAt(4, 8); Assert.Equal(IrScalarState.Constant, value.State); Assert.Equal(0x1111u, value.Constant); } [Fact] public void MergeJoinIsReportedForDivergentBlocks() { List program = [ Branch(0, "SCbranchScc0", 1), Mov(4, destination: 8, literal: 1), Nop(8), ]; var ssa = Gen5ScalarSsa.Build(program, userData: []); Assert.True(ssa.IsInsideDivergentMerge(8)); Assert.False(ssa.IsInsideDivergentMerge(0)); } [Fact] public void LoopDoesNotHangTheFixpoint() { List program = [ Mov(0, destination: 8, literal: 1), Nop(4), Branch(8, "SCbranchScc1", -3), Nop(12), ]; var ssa = Gen5ScalarSsa.Build(program, userData: []); Assert.NotEmpty(ssa.Graph.LoopHeaders); Assert.Equal(IrScalarState.Constant, ssa.GetScalarAt(12, 8).State); } }