Editor/Prism/Compiler/Ir/HelperFunction.cs

IR helper function representation used by the Editor Prism compiler. Defines parameter passing modifiers and a HelperFunction type that stores name, return type, parameters, emitted HLSL/Slang bodies, dependencies, metadata and simple conflict detection.

Reflection
using Editor.Prism.Core;

namespace Editor.Prism.Compiler.Ir;

/// <summary>How a helper parameter is passed.</summary>
public enum IrParamModifier
{
	/// <summary>Passed by value.</summary>
	In,
	/// <summary>Written by the callee.</summary>
	Out,
	/// <summary>Read and written by the callee.</summary>
	InOut,
	/// <summary>Uniform across the draw call.</summary>
	Uniform
}

/// <summary>One parameter of a helper or IR function.</summary>
public readonly record struct HelperParam( string Name, ShaderType Type, IrParamModifier Modifier = IrParamModifier.In )
{
	/// <summary>HLSL spelling of the parameter, e.g. <c>float3 c</c>.</summary>
	public string Hlsl => Modifier switch
	{
		IrParamModifier.Out => $"out {Type.Hlsl} {Name}",
		IrParamModifier.InOut => $"inout {Type.Hlsl} {Name}",
		IrParamModifier.Uniform => $"uniform {Type.Hlsl} {Name}",
		_ => $"{Type.Hlsl} {Name}"
	};

	/// <inheritdoc/>
	public override string ToString() => Hlsl;
}

/// <summary>
/// A reusable function emitted once per module, carrying a body per backend.
/// <para>
/// Helpers are keyed by <see cref="Name"/> and deduplicated per <see cref="IrModule"/>. A same-name,
/// different-body collision is a hard compile error naming both contributors — unlike the built-in
/// editor's process-global function table, which silently keeps whichever body registered first and
/// lets two unrelated nodes share one implementation forever.
/// </para>
/// </summary>
public sealed class HelperFunction
{
	/// <summary>Declare a helper.</summary>
	public HelperFunction( string name, ShaderType returnType, IReadOnlyList<HelperParam> parameters )
	{
		Name = name;
		ReturnType = returnType;
		Parameters = parameters ?? Array.Empty<HelperParam>();
	}

	/// <summary>Unique name. Also the dedup key and the emitted function name.</summary>
	public string Name { get; }

	/// <summary>Return type.</summary>
	public ShaderType ReturnType { get; }

	/// <summary>Parameters, in order.</summary>
	public IReadOnlyList<HelperParam> Parameters { get; }

	/// <summary>Complete HLSL definition, including the signature.</summary>
	public string Hlsl { get; init; }

	/// <summary>Complete Slang definition. Null means "reuse <see cref="Hlsl"/> verbatim".</summary>
	public string Slang { get; init; }

	/// <summary>Other helpers this one calls. Emitted before it, topologically sorted.</summary>
	public IReadOnlyList<HelperFunction> Requires { get; init; } = Array.Empty<HelperFunction>();

	/// <summary>Includes this helper needs, added to the module's include set.</summary>
	public IReadOnlyList<string> Includes { get; init; } = Array.Empty<string>();

	/// <summary>Stages the helper is legal in.</summary>
	public StageMask Stages { get; init; } = StageMask.All;

	/// <summary>Minimum shader model the body requires.</summary>
	public ShaderModel MinShaderModel { get; init; }

	/// <summary>Capabilities the body requires.</summary>
	public IReadOnlyList<Capability> Capabilities { get; init; } = Array.Empty<Capability>();

	/// <summary>False when the helper has side effects or depends on neighbouring lanes.</summary>
	public bool Pure { get; init; } = true;

	/// <summary>The dedup key. Always the name.</summary>
	public string DedupKey => Name;

	/// <summary>The body to emit for a given backend id.</summary>
	public string BodyFor( string backendId ) =>
		backendId == PrismConstants.BackendSlang ? Slang ?? Hlsl : Hlsl;

	/// <summary>
	/// True when two helpers share a name but not a body — the collision that must be reported as a
	/// compile error rather than silently resolved.
	/// </summary>
	public bool ConflictsWith( HelperFunction other )
	{
		if ( other is null || ReferenceEquals( this, other ) ) return false;
		if ( Name != other.Name ) return false;

		return Hlsl != other.Hlsl || Slang != other.Slang;
	}

	/// <summary>The signature line, without a body, in HLSL.</summary>
	public string SignatureHlsl =>
		$"{ReturnType.Hlsl} {Name}( {string.Join( ", ", Parameters.Select( x => x.Hlsl ) )} )";

	/// <inheritdoc/>
	public override string ToString() => SignatureHlsl;
}