mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 06:59:45 +08:00
[shader] add scalar reaching definitions
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,498 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharpEmu.ShaderCompiler.Ir;
|
||||
|
||||
public enum IrScalarState
|
||||
{
|
||||
Unknown,
|
||||
Constant,
|
||||
Merged,
|
||||
}
|
||||
|
||||
public enum IrReachingState
|
||||
{
|
||||
None,
|
||||
Single,
|
||||
Multiple,
|
||||
}
|
||||
|
||||
public readonly record struct IrReachingDefinition(IrReachingState State, uint DefinitionPc)
|
||||
{
|
||||
public static readonly IrReachingDefinition None = new(IrReachingState.None, 0);
|
||||
|
||||
public static readonly IrReachingDefinition Multiple = new(IrReachingState.Multiple, 0);
|
||||
|
||||
public static IrReachingDefinition At(uint pc) => new(IrReachingState.Single, pc);
|
||||
|
||||
public IrReachingDefinition Join(IrReachingDefinition other)
|
||||
{
|
||||
if (State == IrReachingState.None)
|
||||
{
|
||||
return other;
|
||||
}
|
||||
|
||||
if (other.State == IrReachingState.None)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
if (State == IrReachingState.Single &&
|
||||
other.State == IrReachingState.Single &&
|
||||
DefinitionPc == other.DefinitionPc)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
return Multiple;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct IrScalarValue(IrScalarState State, uint Constant)
|
||||
{
|
||||
public static readonly IrScalarValue Unknown = new(IrScalarState.Unknown, 0);
|
||||
|
||||
public static readonly IrScalarValue Merged = new(IrScalarState.Merged, 0);
|
||||
|
||||
public static IrScalarValue FromConstant(uint value) => new(IrScalarState.Constant, value);
|
||||
|
||||
public bool IsResolved => State == IrScalarState.Constant;
|
||||
|
||||
public IrScalarValue Join(IrScalarValue other)
|
||||
{
|
||||
if (State == IrScalarState.Unknown)
|
||||
{
|
||||
return other;
|
||||
}
|
||||
|
||||
if (other.State == IrScalarState.Unknown)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
if (State == IrScalarState.Constant &&
|
||||
other.State == IrScalarState.Constant &&
|
||||
Constant == other.Constant)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
return Merged;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Gen5ScalarSsa
|
||||
{
|
||||
public const int ScalarRegisterCount = 256;
|
||||
|
||||
private Gen5ScalarSsa(
|
||||
IrControlFlowGraph graph,
|
||||
IReadOnlyList<IrScalarValue[]> entryState,
|
||||
IReadOnlyList<IrScalarValue[]> exitState,
|
||||
IReadOnlyList<IrReachingDefinition[]> entryDefinitions,
|
||||
IReadOnlyDictionary<uint, int> blockByPc,
|
||||
IReadOnlyList<Gen5ShaderInstruction> instructions)
|
||||
{
|
||||
Graph = graph;
|
||||
_entryState = entryState;
|
||||
_exitState = exitState;
|
||||
_entryDefinitions = entryDefinitions;
|
||||
_blockByPc = blockByPc;
|
||||
_instructions = instructions;
|
||||
}
|
||||
|
||||
private readonly IReadOnlyList<IrReachingDefinition[]> _entryDefinitions;
|
||||
|
||||
public IrControlFlowGraph Graph { get; }
|
||||
|
||||
private readonly IReadOnlyList<IrScalarValue[]> _entryState;
|
||||
private readonly IReadOnlyList<IrScalarValue[]> _exitState;
|
||||
private readonly IReadOnlyDictionary<uint, int> _blockByPc;
|
||||
private readonly IReadOnlyList<Gen5ShaderInstruction> _instructions;
|
||||
|
||||
public static Gen5ScalarSsa Build(
|
||||
IReadOnlyList<Gen5ShaderInstruction> instructions,
|
||||
IReadOnlyList<uint> userData,
|
||||
IIrBranchResolver? resolver = null)
|
||||
{
|
||||
resolver ??= Gen5IrBranchResolver.Instance;
|
||||
var graph = IrControlFlowGraph.Build(instructions, resolver);
|
||||
var blockCount = graph.Blocks.Count;
|
||||
|
||||
var entry = new List<IrScalarValue[]>(blockCount);
|
||||
var exit = new List<IrScalarValue[]>(blockCount);
|
||||
var defEntry = new List<IrReachingDefinition[]>(blockCount);
|
||||
var defExit = new List<IrReachingDefinition[]>(blockCount);
|
||||
for (var index = 0; index < blockCount; index++)
|
||||
{
|
||||
entry.Add(NewState());
|
||||
exit.Add(NewState());
|
||||
defEntry.Add(NewDefinitions());
|
||||
defExit.Add(NewDefinitions());
|
||||
}
|
||||
|
||||
if (blockCount > 0)
|
||||
{
|
||||
var initial = entry[0];
|
||||
for (var index = 0; index < userData.Count && index < ScalarRegisterCount; index++)
|
||||
{
|
||||
initial[index] = IrScalarValue.FromConstant(userData[index]);
|
||||
}
|
||||
}
|
||||
|
||||
var blockByPc = new Dictionary<uint, int>();
|
||||
for (var blockIndex = 0; blockIndex < blockCount; blockIndex++)
|
||||
{
|
||||
var range = graph.Blocks[blockIndex];
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
if (instruction.Pc >= range.StartPc && instruction.Pc < range.EndPc)
|
||||
{
|
||||
blockByPc[instruction.Pc] = blockIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var worklist = new Queue<int>();
|
||||
for (var index = 0; index < blockCount; index++)
|
||||
{
|
||||
worklist.Enqueue(index);
|
||||
}
|
||||
|
||||
var visits = new int[blockCount];
|
||||
const int visitLimit = 8;
|
||||
while (worklist.Count > 0)
|
||||
{
|
||||
var blockIndex = worklist.Dequeue();
|
||||
if (visits[blockIndex]++ > visitLimit)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var state = (IrScalarValue[])entry[blockIndex].Clone();
|
||||
if (graph.Predecessors[blockIndex].Count > 0)
|
||||
{
|
||||
state = NewState();
|
||||
var first = true;
|
||||
foreach (var predecessor in graph.Predecessors[blockIndex])
|
||||
{
|
||||
var incoming = exit[predecessor];
|
||||
for (var register = 0; register < ScalarRegisterCount; register++)
|
||||
{
|
||||
state[register] = first
|
||||
? incoming[register]
|
||||
: state[register].Join(incoming[register]);
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (blockIndex == 0)
|
||||
{
|
||||
for (var register = 0; register < userData.Count && register < ScalarRegisterCount; register++)
|
||||
{
|
||||
state[register] = state[register].Join(
|
||||
IrScalarValue.FromConstant(userData[register]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entry[blockIndex] = state;
|
||||
|
||||
var definitions = NewDefinitions();
|
||||
if (graph.Predecessors[blockIndex].Count == 0)
|
||||
{
|
||||
for (var register = 0; register < userData.Count && register < ScalarRegisterCount; register++)
|
||||
{
|
||||
definitions[register] = IrReachingDefinition.At(uint.MaxValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var first = true;
|
||||
foreach (var predecessor in graph.Predecessors[blockIndex])
|
||||
{
|
||||
var incoming = defExit[predecessor];
|
||||
for (var register = 0; register < ScalarRegisterCount; register++)
|
||||
{
|
||||
definitions[register] = first
|
||||
? incoming[register]
|
||||
: definitions[register].Join(incoming[register]);
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
defEntry[blockIndex] = definitions;
|
||||
|
||||
var computed = Transfer(instructions, graph.Blocks[blockIndex], state);
|
||||
var computedDefinitions = TransferDefinitions(
|
||||
instructions,
|
||||
graph.Blocks[blockIndex],
|
||||
definitions);
|
||||
var changed = !SameState(exit[blockIndex], computed) ||
|
||||
!SameDefinitions(defExit[blockIndex], computedDefinitions);
|
||||
exit[blockIndex] = computed;
|
||||
defExit[blockIndex] = computedDefinitions;
|
||||
if (changed)
|
||||
{
|
||||
foreach (var successor in graph.Successors[blockIndex])
|
||||
{
|
||||
worklist.Enqueue(successor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Gen5ScalarSsa(graph, entry, exit, defEntry, blockByPc, instructions);
|
||||
}
|
||||
|
||||
public IrScalarValue GetScalarAt(uint pc, uint register)
|
||||
{
|
||||
if (register >= ScalarRegisterCount || !_blockByPc.TryGetValue(pc, out var blockIndex))
|
||||
{
|
||||
return IrScalarValue.Unknown;
|
||||
}
|
||||
|
||||
var state = (IrScalarValue[])_entryState[blockIndex].Clone();
|
||||
var range = _graphRange(blockIndex);
|
||||
foreach (var instruction in _instructions)
|
||||
{
|
||||
if (instruction.Pc < range.StartPc || instruction.Pc >= range.EndPc)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (instruction.Pc >= pc)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Apply(instruction, state);
|
||||
}
|
||||
|
||||
return state[register];
|
||||
}
|
||||
|
||||
public IrReachingDefinition GetReachingDefinitionAt(uint pc, uint register)
|
||||
{
|
||||
if (register >= ScalarRegisterCount || !_blockByPc.TryGetValue(pc, out var blockIndex))
|
||||
{
|
||||
return IrReachingDefinition.None;
|
||||
}
|
||||
|
||||
var definitions = (IrReachingDefinition[])_entryDefinitions[blockIndex].Clone();
|
||||
var range = _graphRange(blockIndex);
|
||||
foreach (var instruction in _instructions)
|
||||
{
|
||||
if (instruction.Pc < range.StartPc || instruction.Pc >= range.EndPc)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (instruction.Pc >= pc)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ApplyDefinitions(instruction, definitions);
|
||||
}
|
||||
|
||||
return definitions[register];
|
||||
}
|
||||
|
||||
public bool IsInsideDivergentMerge(uint pc) =>
|
||||
_blockByPc.TryGetValue(pc, out var blockIndex) &&
|
||||
_graphPredecessorCount(blockIndex) > 1;
|
||||
|
||||
private IrBlockRange _graphRange(int blockIndex) => Graph.Blocks[blockIndex];
|
||||
|
||||
private int _graphPredecessorCount(int blockIndex) => Graph.Predecessors[blockIndex].Count;
|
||||
|
||||
private static IrScalarValue[] NewState()
|
||||
{
|
||||
var state = new IrScalarValue[ScalarRegisterCount];
|
||||
for (var index = 0; index < state.Length; index++)
|
||||
{
|
||||
state[index] = IrScalarValue.Unknown;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private static IrReachingDefinition[] NewDefinitions()
|
||||
{
|
||||
var definitions = new IrReachingDefinition[ScalarRegisterCount];
|
||||
for (var index = 0; index < definitions.Length; index++)
|
||||
{
|
||||
definitions[index] = IrReachingDefinition.None;
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private static bool SameDefinitions(IrReachingDefinition[] left, IrReachingDefinition[] right)
|
||||
{
|
||||
for (var index = 0; index < left.Length; index++)
|
||||
{
|
||||
if (!left[index].Equals(right[index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IrReachingDefinition[] TransferDefinitions(
|
||||
IReadOnlyList<Gen5ShaderInstruction> instructions,
|
||||
IrBlockRange range,
|
||||
IrReachingDefinition[] entry)
|
||||
{
|
||||
var definitions = (IrReachingDefinition[])entry.Clone();
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
if (instruction.Pc < range.StartPc || instruction.Pc >= range.EndPc)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplyDefinitions(instruction, definitions);
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
public const uint VccLo = 106;
|
||||
|
||||
public const uint VccHi = 107;
|
||||
|
||||
/// <summary>
|
||||
/// VOPC compares and the VOP2 carry forms write VCC without naming it: the ISA
|
||||
/// makes the destination implicit in the encoding, so the decoded instruction
|
||||
/// carries no destination operand for it. Modelling that here (rather than in
|
||||
/// the shared decoder) keeps the linear evaluator's behaviour untouched while
|
||||
/// letting the dataflow see that VCC was written.
|
||||
/// </summary>
|
||||
public static bool WritesVccImplicitly(Gen5ShaderInstruction instruction)
|
||||
{
|
||||
if (instruction.Encoding == Gen5ShaderEncoding.Vopc)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return instruction.Encoding == Gen5ShaderEncoding.Vop2 &&
|
||||
instruction.Opcode is
|
||||
"VAddCoCiU32" or
|
||||
"VSubCoCiU32" or
|
||||
"VSubrevCoCiU32";
|
||||
}
|
||||
|
||||
private static void ApplyDefinitions(
|
||||
Gen5ShaderInstruction instruction,
|
||||
IrReachingDefinition[] definitions)
|
||||
{
|
||||
foreach (var destination in instruction.Destinations)
|
||||
{
|
||||
if (destination.Kind != Gen5OperandKind.ScalarRegister ||
|
||||
destination.Value >= ScalarRegisterCount)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
definitions[destination.Value] = IrReachingDefinition.At(instruction.Pc);
|
||||
}
|
||||
|
||||
if (WritesVccImplicitly(instruction))
|
||||
{
|
||||
definitions[VccLo] = IrReachingDefinition.At(instruction.Pc);
|
||||
definitions[VccHi] = IrReachingDefinition.At(instruction.Pc);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool SameState(IrScalarValue[] left, IrScalarValue[] right)
|
||||
{
|
||||
for (var index = 0; index < left.Length; index++)
|
||||
{
|
||||
if (!left[index].Equals(right[index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IrScalarValue[] Transfer(
|
||||
IReadOnlyList<Gen5ShaderInstruction> instructions,
|
||||
IrBlockRange range,
|
||||
IrScalarValue[] entry)
|
||||
{
|
||||
var state = (IrScalarValue[])entry.Clone();
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
if (instruction.Pc < range.StartPc || instruction.Pc >= range.EndPc)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Apply(instruction, state);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private static void Apply(Gen5ShaderInstruction instruction, IrScalarValue[] state)
|
||||
{
|
||||
var resolved = ResolveResult(instruction, state);
|
||||
foreach (var destination in instruction.Destinations)
|
||||
{
|
||||
if (destination.Kind != Gen5OperandKind.ScalarRegister ||
|
||||
destination.Value >= ScalarRegisterCount)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
state[destination.Value] = resolved;
|
||||
}
|
||||
}
|
||||
|
||||
private static IrScalarValue ResolveResult(
|
||||
Gen5ShaderInstruction instruction,
|
||||
IrScalarValue[] state)
|
||||
{
|
||||
if (instruction.Destinations.Count != 1)
|
||||
{
|
||||
return IrScalarValue.Unknown;
|
||||
}
|
||||
|
||||
return instruction.Opcode switch
|
||||
{
|
||||
"SMov" or "SMovB32" => Source(instruction, state, 0),
|
||||
_ => IrScalarValue.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
private static IrScalarValue Source(
|
||||
Gen5ShaderInstruction instruction,
|
||||
IrScalarValue[] state,
|
||||
int index)
|
||||
{
|
||||
if (index >= instruction.Sources.Count)
|
||||
{
|
||||
return IrScalarValue.Unknown;
|
||||
}
|
||||
|
||||
var source = instruction.Sources[index];
|
||||
return source.Kind switch
|
||||
{
|
||||
Gen5OperandKind.ScalarRegister when source.Value < ScalarRegisterCount =>
|
||||
state[source.Value],
|
||||
Gen5OperandKind.LiteralConstant => IrScalarValue.FromConstant(source.Value),
|
||||
_ => IrScalarValue.Unknown,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user