From 166152e99cc0a021468d0cc96493d26fba010998 Mon Sep 17 00:00:00 2001 From: ParantezTech <12572227+par274@users.noreply.github.com> Date: Thu, 30 Jul 2026 05:25:39 +0300 Subject: [PATCH] [shader] add scalar reaching definitions Co-Authored-By: Claude Opus 5 --- .../Ir/Gen5ScalarSsa.cs | 498 ++++++++++++++++++ 1 file changed, 498 insertions(+) create mode 100644 src/SharpEmu.ShaderCompiler/Ir/Gen5ScalarSsa.cs diff --git a/src/SharpEmu.ShaderCompiler/Ir/Gen5ScalarSsa.cs b/src/SharpEmu.ShaderCompiler/Ir/Gen5ScalarSsa.cs new file mode 100644 index 00000000..7b5d715e --- /dev/null +++ b/src/SharpEmu.ShaderCompiler/Ir/Gen5ScalarSsa.cs @@ -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 entryState, + IReadOnlyList exitState, + IReadOnlyList entryDefinitions, + IReadOnlyDictionary blockByPc, + IReadOnlyList instructions) + { + Graph = graph; + _entryState = entryState; + _exitState = exitState; + _entryDefinitions = entryDefinitions; + _blockByPc = blockByPc; + _instructions = instructions; + } + + private readonly IReadOnlyList _entryDefinitions; + + public IrControlFlowGraph Graph { get; } + + private readonly IReadOnlyList _entryState; + private readonly IReadOnlyList _exitState; + private readonly IReadOnlyDictionary _blockByPc; + private readonly IReadOnlyList _instructions; + + public static Gen5ScalarSsa Build( + IReadOnlyList instructions, + IReadOnlyList userData, + IIrBranchResolver? resolver = null) + { + resolver ??= Gen5IrBranchResolver.Instance; + var graph = IrControlFlowGraph.Build(instructions, resolver); + var blockCount = graph.Blocks.Count; + + var entry = new List(blockCount); + var exit = new List(blockCount); + var defEntry = new List(blockCount); + var defExit = new List(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(); + 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(); + 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 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; + + /// + /// 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. + /// + 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 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, + }; + } +}