sharpemu/tests/SharpEmu.ShaderCompiler.Tests/Resources/DeviceAddressRangePlannerTe...

192 lines
8.2 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.ShaderCompiler;
using SharpEmu.ShaderCompiler.Resources;
using Xunit;
using static SharpEmu.ShaderCompiler.Tests.Resources.ResourceTestProgram;
namespace SharpEmu.ShaderCompiler.Tests.Resources;
public sealed class DeviceAddressRangePlannerTests
{
// flat_store through (aperture_hi << 32) | offset: the high dword comes from an
// aperture operand, so the store targets LDS or scratch and owns no device range.
[Theory]
[InlineData(Gen5InlineConstants.SharedBase, FlatAddressSpace.Shared)]
[InlineData(Gen5InlineConstants.PrivateBase, FlatAddressSpace.Private)]
public void FlatAddressFromAnAperture_IsLocalAndHasNoDeviceRange(uint aperture, FlatAddressSpace expected)
{
var plan = Extract(Program(
Sop1(0, "SMovB32", 1, Gen5Operand.Source(aperture)),
Vop1(4, "VMovB32", 1, Gen5Operand.Scalar(1)),
Vop1(8, "VMovB32", 0, Operand(16)),
GlobalAccess(12, "FlatStoreDword", 0, vectorAddress: 0),
EndProgram(20)));
Assert.Empty(plan.DeviceAddressRanges);
Assert.True(plan.Memory.TryGetIndex(12, 0, out var index));
Assert.Equal(expected, plan.Memory[index].AddressSpace);
}
// A high dword clamped between the hard-coded PS5 aperture bounds
// (v_med3_u32 0x80000000, 0x70000000, sp) always lands in LDS or scratch.
[Fact]
public void FlatAddressClampedIntoTheApertures_IsLocal()
{
var plan = Extract(Program(
Vop3(0, "VMed3U32", 1, Operand(0x8000_0000), Operand(0x7000_0000), Gen5Operand.Vector(7)),
Vop1(8, "VMovB32", 0, Operand(16)),
GlobalAccess(12, "FlatStoreDword", 0, vectorAddress: 0),
EndProgram(20)));
Assert.Empty(plan.DeviceAddressRanges);
Assert.True(plan.Memory.TryGetIndex(12, 0, out var index));
Assert.Equal(FlatAddressSpace.SharedOrPrivate, plan.Memory[index].AddressSpace);
}
// After v_cmpx the clamp is a lane-masked write whose inactive lanes keep an
// unknown value; those lanes do not store, so the address is still local.
[Fact]
public void ClampedAddressWrittenUnderTheStoresExecMask_IsLocal()
{
var plan = Extract(Program(
Vopc(0, "VCmpxLtU32", Operand(3), 5),
Vop3(4, "VMed3U32", 1, Operand(0x8000_0000), Operand(0x7000_0000), Gen5Operand.Vector(7)),
Vop1(12, "VMovB32", 0, Operand(16)),
GlobalAccess(16, "FlatStoreDword", 0, vectorAddress: 0),
EndProgram(24)));
Assert.Empty(plan.DeviceAddressRanges);
Assert.True(plan.Memory.TryGetIndex(16, 0, out var index));
Assert.Equal(FlatAddressSpace.SharedOrPrivate, plan.Memory[index].AddressSpace);
}
// Without an aperture the same flat store is a device access and keeps its range.
[Fact]
public void FlatAddressWithoutAperture_StaysGlobal()
{
var plan = Extract(Program(
Vop1(0, "VMovB32", 1, Operand(0)),
Vop1(4, "VMovB32", 0, Operand(0x1000)),
GlobalAccess(8, "FlatStoreDword", 0, vectorAddress: 0),
EndProgram(16)));
var range = Assert.Single(plan.DeviceAddressRanges);
Assert.True(range.Written);
Assert.True(plan.Memory.TryGetIndex(8, 0, out var index));
Assert.Equal(FlatAddressSpace.Global, plan.Memory[index].AddressSpace);
}
// A 32-bit read returns the aperture's high dword; a 64-bit read the full address.
[Fact]
public void ApertureOperands_DecodeConsistentlyAsHighDwordAndAddress()
{
Assert.True(Gen5InlineConstants.TryDecode(Gen5InlineConstants.SharedBase, out var shared));
Assert.Equal(Gen5InlineConstants.SharedApertureHigh, shared);
Assert.True(Gen5InlineConstants.TryDecode(Gen5InlineConstants.PrivateLimit, out var privateLimit));
Assert.Equal(Gen5InlineConstants.PrivateApertureHigh, privateLimit);
Assert.Equal(0x7000_0000_0000_0000ul, Gen5InlineConstants.DecodeAperture64(Gen5InlineConstants.SharedBase));
Assert.Equal(0x7000_0000_FFFF_FFFFul, Gen5InlineConstants.DecodeAperture64(Gen5InlineConstants.SharedLimit));
Assert.Equal(0x8000_0000_0000_0000ul, Gen5InlineConstants.DecodeAperture64(Gen5InlineConstants.PrivateBase));
}
[Fact]
public void BoundedOffsets_GiveTheLargestExtent()
{
var plan = Extract(Program(
Vop1(0, "VMovB32", 0, Operand(0)),
GlobalAccess(4, "GlobalLoadDword", 0, offset: 8),
GlobalAccess(12, "GlobalLoadDwordx4", 0, offset: 32, dwords: 4),
EndProgram(20)));
var range = Assert.Single(plan.DeviceAddressRanges);
Assert.True(range.Bounded);
Assert.True(range.Plannable);
Assert.False(range.Written);
Assert.Equal(8, range.FirstByte);
Assert.Equal(48, range.EndByte);
Assert.Equal(40ul, range.Extent);
Assert.Equal(2, range.MemoryIndices.Count);
var evaluated = Assert.Single(DeviceAddressRangePlanner.Evaluate(plan, Inputs([0x1000, 0])));
Assert.True(evaluated.Planned);
Assert.Equal(0x1008ul, evaluated.Base);
Assert.Equal(40ul, evaluated.Size);
}
// A store 8 bytes below the handle starts the range there; the range covers the
// lowest byte through the end of the highest access.
[Fact]
public void NegativeOffset_StartsTheRangeAtTheLowestByte()
{
var plan = Extract(Program(
Vop1(0, "VMovB32", 0, Operand(0)),
GlobalAccess(4, "GlobalStoreDword", 0, offset: -8),
GlobalAccess(12, "GlobalLoadDwordx2", 0, offset: 8, dwords: 2),
EndProgram(20)));
var range = Assert.Single(plan.DeviceAddressRanges);
Assert.True(range.Bounded);
Assert.True(range.Written);
Assert.Equal(-8, range.FirstByte);
Assert.Equal(16, range.EndByte);
Assert.Equal(24ul, range.Extent);
var evaluated = Assert.Single(DeviceAddressRangePlanner.Evaluate(plan, Inputs([0x2008, 0])));
Assert.True(evaluated.Planned);
Assert.Equal(0x2000ul, evaluated.Base);
Assert.Equal(24ul, evaluated.Size);
var storeOnly = Extract(Program(
Vop1(0, "VMovB32", 0, Operand(0)),
GlobalAccess(4, "GlobalStoreDword", 0, offset: -8),
EndProgram(12)));
var single = Assert.Single(DeviceAddressRangePlanner.Evaluate(storeOnly, Inputs([0x2008, 0])));
Assert.Equal(0x2000ul, single.Base);
Assert.Equal(4ul, single.Size);
}
[Fact]
public void RuntimeOffset_ExtendsToTheMappedEndUpToTheCap()
{
var plan = Extract(Program(
GlobalAccess(0, "GlobalLoadDword", 0, offset: 8, vectorAddress: 0),
EndProgram(8)));
var range = Assert.Single(plan.DeviceAddressRanges);
Assert.False(range.Bounded);
Assert.True(range.Plannable);
var evaluated = Assert.Single(DeviceAddressRangePlanner.Evaluate(plan, Inputs([0x1000, 0])));
Assert.True(evaluated.Planned);
Assert.Equal(DeviceAddressRangePlanner.MaxRangeBytes, evaluated.Size);
}
[Fact]
public void UnplannableHandle_DoesNotFailThePlan()
{
var plan = Extract(Program(GlobalAccess(0, "FlatLoadDword", 0, vectorAddress: 2), EndProgram(8)));
Assert.True(plan.Info.UsesDeviceAddresses);
var range = Assert.Single(plan.DeviceAddressRanges);
Assert.False(range.Plannable);
var evaluated = Assert.Single(DeviceAddressRangePlanner.Evaluate(plan, Inputs([])));
Assert.False(evaluated.Planned);
Assert.Equal(0ul, evaluated.Size);
}
[Fact]
public void WrittenUnplannableHandle_IsReportedForTheHost()
{
var plan = Extract(Program(GlobalAccess(0, "FlatStoreDword", 0, vectorAddress: 2), EndProgram(8)));
var range = Assert.Single(plan.DeviceAddressRanges);
Assert.True(range.Written);
Assert.False(range.Plannable);
var snapshot = new ResourceSnapshot();
var specialization = new ResourceSpecialization();
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([]), ref snapshot, ref specialization));
var reported = Assert.Single(snapshot.DeviceAddressRanges);
Assert.True(reported.Written);
Assert.False(reported.Planned);
}
}