feat(shader): refactor and enhance shader pipeline

Refactored the shader compilation pipeline to introduce modularity, improve performance, and enhance maintainability. Key changes include:

- Added `ShaderCompilationConfig`, `CompilerOptimizeLevel`, and `ShaderStage` enums.
- Replaced `SM` property with `ShaderModel` in shader models.
- Introduced `ShaderLibrary` for in-memory and disk-based shader caching.
- Refactored `DSLShaderCompiler` and `AntlrShaderCompiler` for better hashing and error handling.
- Centralized shader compilation logic in `ShaderCompilerUtility`.
- Removed legacy shader compilation logic from `IShaderCompiler`.
- Updated `RenderGraph`, `ResourceManager`, and `Material` to integrate with the new caching system.
- Improved memory management with `NativeMemoryManager<T>`.

BREAKING CHANGE: Removed legacy shader compilation methods and replaced them with a new caching and compilation system.
This commit is contained in:
2026-04-11 23:10:39 +09:00
parent f9a6e9cbbe
commit c66fda5332
30 changed files with 630 additions and 500 deletions

View File

@@ -1,8 +1,10 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.DSL.ShaderParser;
using Misaki.HighPerformance.Utilities;
using System.IO.Hashing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@@ -22,9 +24,15 @@ public struct DSLShaderError
internal static class DSLShaderCompiler
{
private static ulong GetPassUniqueId(DSLShaderSemantics shader, PassSemantic pass)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong GetUniqueId(string code)
{
return XxHash64.HashToUInt64(MemoryMarshal.AsBytes($"{shader.name}_{pass.name}".AsSpan()));
if (string.IsNullOrEmpty(code))
{
return 0;
}
return XxHash64.HashToUInt64(MemoryMarshal.AsBytes(code.AsSpan()));
}
private static PipelineState MeragePipeline(PipelineSemantic? semantic, PipelineState parent)
@@ -132,9 +140,13 @@ internal static class DSLShaderCompiler
var pixelShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.pixelShader.entry };
var asHash = Hash.Combine64(GetUniqueId(amplificationShaderCode.code), GetUniqueId(amplificationShaderCode.entryPoint));
var msHash = Hash.Combine64(GetUniqueId(meshShaderCode.code), GetUniqueId(meshShaderCode.entryPoint));
var psHash = Hash.Combine64(GetUniqueId(pixelShaderCode.code), GetUniqueId(pixelShaderCode.entryPoint));
passes[i] = new PassDescriptor
{
identifier = GetPassUniqueId(semantics, pass),
identifier = Hash.Combine64(GetUniqueId(semantics.name + pass.name), asHash, msHash, psHash),
name = pass.name,
amplificationShaderCode = amplificationShaderCode,
@@ -289,7 +301,6 @@ internal static class DSLShaderCompiler
return new ComputeShaderDescriptor
{
identifier = XxHash64.HashToUInt64(MemoryMarshal.AsBytes(semantics.name.AsSpan())),
name = semantics.name,
propertyBufferSize = propertyInfo.size,
shaderModel = semantics.shaderModel,

View File

@@ -99,6 +99,40 @@ public class AntlrShaderCompiler
}
}
private static bool TryGetShaderModel(string model, List<DSLShaderError> errors, out ShaderModel shaderModel)
{
if (string.IsNullOrEmpty(model))
{
shaderModel = ShaderModel.SM_6_6; // Default to lowest supported shader model for compute shaders
}
else
{
switch (model)
{
case "6_6":
shaderModel = ShaderModel.SM_6_6;
break;
case "6_7":
shaderModel = ShaderModel.SM_6_7;
break;
case "6_8":
shaderModel = ShaderModel.SM_6_8;
break;
default:
shaderModel = default;
errors.Add(new DSLShaderError
{
message = $"Unknown shader model '{model}'.",
line = 0,
column = 0
});
return false;
}
}
return true;
}
public static DSLComputeShaderSemantics? ConvertToComputeSemantics(ComputeShaderModel model, out List<DSLShaderError> errors)
{
errors = new List<DSLShaderError>();
@@ -122,29 +156,9 @@ public class AntlrShaderCompiler
hlsl = model.Hlsl?.Code
};
if (string.IsNullOrEmpty(model.SM))
if (TryGetShaderModel(model.ShaderModel, errors, out var shaderModel))
{
semantics.shaderModel = ShaderModel.SM_6_8; // Default to highest supported shader model
}
else
{
semantics.shaderModel = model.SM.ToLower() switch
{
"6_6" => ShaderModel.SM_6_6,
"6_7" => ShaderModel.SM_6_7,
"6_8" => ShaderModel.SM_6_8,
_ => ShaderModel.Invalid
};
if (semantics.shaderModel == ShaderModel.Invalid)
{
errors.Add(new DSLShaderError
{
message = $"Unknown shader model '{model.SM}'.",
line = 0,
column = 0
});
}
semantics.shaderModel = shaderModel;
}
if (model.Keywords != null)
@@ -207,65 +221,6 @@ public class AntlrShaderCompiler
return semantics;
}
public static DSLShaderSemantics? ConvertToSemantics(GraphicsShaderModel model, out List<DSLShaderError> errors)
{
errors = new List<DSLShaderError>();
if (string.IsNullOrWhiteSpace(model.Name))
{
errors.Add(new DSLShaderError
{
message = "Shader name cannot be empty.",
line = 0,
column = 0
});
return null;
}
var semantics = new DSLShaderSemantics
{
name = model.Name,
pipeline = ConvertPipeline(model.Pipeline, errors)
};
if (string.IsNullOrEmpty(model.SM))
{
semantics.shaderModel = ShaderModel.SM_6_8; // Default to highest supported shader model
}
else
{
semantics.shaderModel = model.SM.ToLower() switch
{
"6_6" => ShaderModel.SM_6_6,
"6_7" => ShaderModel.SM_6_7,
"6_8" => ShaderModel.SM_6_8,
_ => ShaderModel.Invalid
};
if (semantics.shaderModel == ShaderModel.Invalid)
{
errors.Add(new DSLShaderError
{
message = $"Unknown shader model '{model.SM}'.",
line = 0,
column = 0
});
}
}
foreach (var pass in model.Passes)
{
var passSemantic = ConvertPass(pass, errors);
if (passSemantic != null)
{
semantics.passes ??= new List<PassSemantic>();
semantics.passes.Add(passSemantic);
}
}
return semantics;
}
private static PipelineSemantic? ConvertPipeline(PipelineBlockModel? pipeline, List<DSLShaderError> errors)
{
if (pipeline == null || pipeline.Statements.Count == 0)
@@ -394,6 +349,45 @@ public class AntlrShaderCompiler
return semantic;
}
public static DSLShaderSemantics? ConvertToSemantics(GraphicsShaderModel model, out List<DSLShaderError> errors)
{
errors = new List<DSLShaderError>();
if (string.IsNullOrWhiteSpace(model.Name))
{
errors.Add(new DSLShaderError
{
message = "Shader name cannot be empty.",
line = 0,
column = 0
});
return null;
}
var semantics = new DSLShaderSemantics
{
name = model.Name,
pipeline = ConvertPipeline(model.Pipeline, errors)
};
if (TryGetShaderModel(model.ShaderModel, errors, out var shaderModel))
{
semantics.shaderModel = shaderModel;
}
foreach (var pass in model.Passes)
{
var passSemantic = ConvertPass(pass, errors);
if (passSemantic != null)
{
semantics.passes ??= new List<PassSemantic>();
semantics.passes.Add(passSemantic);
}
}
return semantics;
}
private class ErrorListener : BaseErrorListener, IAntlrErrorListener<int>, IAntlrErrorListener<IToken>
{
private readonly List<DSLShaderError> _errors;

View File

@@ -38,7 +38,7 @@ internal class ComputeShaderVisitor : GhostComputeShaderParserBaseVisitor<object
var computeBody = context.computeBody();
if (computeBody != null)
{
compute.SM = computeBody.shaderModel()?.GetText() ?? string.Empty;
compute.ShaderModel = computeBody.shaderModel()?.GetText() ?? string.Empty;
foreach (var definesBlock in computeBody.definesBlock())
{

View File

@@ -3,7 +3,7 @@ namespace Ghost.DSL.ShaderParser.Model;
public class GraphicsShaderModel
{
public string Name { get; set; } = string.Empty;
public string SM { get; set; } = string.Empty;
public string ShaderModel { get; set; } = string.Empty;
public PipelineBlockModel? Pipeline { get; set; }
public List<PassBlockModel> Passes { get; set; } = new();
public List<FunctionCallModel> FunctionCalls { get; set; } = new();
@@ -12,7 +12,7 @@ public class GraphicsShaderModel
public class ComputeShaderModel
{
public string Name { get; set; } = string.Empty;
public string SM { get; set; } = string.Empty;
public string ShaderModel { get; set; } = string.Empty;
public DefinesBlockModel? Defines { get; set; }
public IncludesBlockModel? Includes { get; set; }
public KeywordsBlockModel? Keywords { get; set; }

View File

@@ -27,7 +27,7 @@ public class ShaderVisitor : GhostShaderParserBaseVisitor<object>
var shaderBody = context.shaderBody();
if (shaderBody != null)
{
shader.SM = shaderBody.shaderModel()?.GetText() ?? string.Empty;
shader.ShaderModel = shaderBody.shaderModel()?.GetText() ?? string.Empty;
foreach (var pipelineBlock in shaderBody.pipelineBlock())
{

View File

@@ -0,0 +1,66 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Editor.Core.Contracts;
public unsafe struct ComputeCompileResult
{
public fixed ulong resultHash[8];
public readonly int count;
public ulong HashCode
{
get
{
var a = Hash.Combine64(resultHash[0], resultHash[1], resultHash[2], resultHash[3]);
var b = Hash.Combine64(resultHash[4], resultHash[5], resultHash[6], resultHash[7]);
return Hash.Combine64(a, b);
}
}
}
public ref struct ShaderCompilationConfig
{
public ReadOnlySpan<string> defines;
public string shaderCode;
public string entryPoint;
public ShaderStage stage;
public ShaderModel model;
public CompilerOptimizeLevel optimizeLevel;
public CompilerOption options;
}
public enum CompilerOptimizeLevel
{
O0,
O1,
O2,
O3
}
[Flags]
public enum CompilerOption
{
None = 0,
KeepDebugInfo = 1 << 0,
KeepReflections = 1 << 1,
WarnAsError = 1 << 2,
SpirvCrossCompile = 1 << 3
}
public enum ShaderStage
{
TaskShader,
MeshShader,
PixelShader,
ComputeShader,
Library // For ray tracing shaders or work graph shaders that don't fit into the traditional shader stages
}
public interface IShaderCompiler : IDisposable
{
Result<UnsafeArray<byte>> Compile(ref readonly ShaderCompilationConfig config, AllocationHandle handle);
}

View File

@@ -1,18 +1,15 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.DXC;
using Ghost.Editor.Core.Contracts;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Utilities;
using System.IO.Hashing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Ghost.DXC;
using static Ghost.DXC.UUID;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Core;
@@ -26,14 +23,17 @@ internal sealed partial class DXCShaderCompiler
(ShaderStage.PixelShader, ShaderModel.SM_6_6) => "ps_6_6",
(ShaderStage.MeshShader, ShaderModel.SM_6_6) => "ms_6_6",
(ShaderStage.ComputeShader, ShaderModel.SM_6_6) => "cs_6_6",
(ShaderStage.Library, ShaderModel.SM_6_6) => "lib_6_6",
(ShaderStage.TaskShader, ShaderModel.SM_6_7) => "as_6_7",
(ShaderStage.PixelShader, ShaderModel.SM_6_7) => "ps_6_7",
(ShaderStage.MeshShader, ShaderModel.SM_6_7) => "ms_6_7",
(ShaderStage.ComputeShader, ShaderModel.SM_6_7) => "cs_6_7",
(ShaderStage.Library, ShaderModel.SM_6_7) => "lib_6_7",
(ShaderStage.TaskShader, ShaderModel.SM_6_8) => "as_6_8",
(ShaderStage.PixelShader, ShaderModel.SM_6_8) => "ps_6_8",
(ShaderStage.MeshShader, ShaderModel.SM_6_8) => "ms_6_8",
(ShaderStage.ComputeShader, ShaderModel.SM_6_8) => "cs_6_8",
(ShaderStage.Library, ShaderModel.SM_6_8) => "lib_6_8",
_ => throw new ArgumentOutOfRangeException(nameof(stage), "Unsupported shader stage or compiler version")
};
}
@@ -105,49 +105,6 @@ internal sealed partial class DXCShaderCompiler
return argsArray;
}
private static Result<string, Error> BuildFinalShaderCode(string shaderPath, ReadOnlySpan<string> includes, string? injectedCode)
{
string shaderCode;
if (shaderPath == "hlsl_block")
{
if (string.IsNullOrEmpty(injectedCode))
{
return Error.InvalidArgument;
}
shaderCode = string.Empty;
}
else
{
if (!File.Exists(shaderPath))
{
return Error.NotFound;
}
shaderCode = File.ReadAllText(shaderPath);
}
var sb = new StringBuilder();
foreach (var includePath in includes)
{
sb.AppendLine($"#include \"{includePath}\"");
}
if (!string.IsNullOrEmpty(injectedCode))
{
sb.AppendLine($"#line 0 \"injected_code\"");
sb.AppendLine(injectedCode);
}
if (!string.IsNullOrEmpty(shaderCode))
{
sb.AppendLine($"#line 0 \"{shaderPath}\"");
sb.AppendLine(shaderCode);
}
return sb.ToString();
}
}
internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
@@ -155,9 +112,6 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
private UniquePtr<IDxcCompiler3> _compiler;
private UniquePtr<IDxcUtils> _utils;
// NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later.
private readonly Dictionary<Key64<ShaderCompileResult>, ShaderCompileResult> _compiledResults;
private bool _disposed;
public DXCShaderCompiler()
@@ -179,8 +133,6 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
_compiler.Attach(pCompiler);
_utils.Attach(pUtils);
_compiledResults = new Dictionary<Key64<ShaderCompileResult>, ShaderCompileResult>();
}
~DXCShaderCompiler()
@@ -188,7 +140,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
Dispose();
}
public Result<Key64<ShaderCompileResult>> Compile(ref readonly ShaderCompilationConfig config)
public Result<UnsafeArray<byte>> Compile(ref readonly ShaderCompilationConfig config, AllocationHandle allocationHandle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -221,7 +173,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
IDxcResult* result = default;
IDxcBlob* bytecodeBlob = default;
try
{
// Compile shader
@@ -268,18 +220,11 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
}
var bytecodeSize = bytecodeBlob->GetBufferSize();
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, Allocator.Persistent);
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, allocationHandle);
NativeMemory.Copy(bytecodeBlob->GetBufferPointer(), bytecode.GetUnsafePtr(), (nuint)bytecodeSize);
var compileResult = new ShaderCompileResult
{
bytecode = bytecode,
hashCode = XxHash64.HashToUInt64(bytecode)
};
_compiledResults[compileResult.hashCode] = compileResult;
return new Key64<ShaderCompileResult>(compileResult.hashCode);
return bytecode;
}
finally
{
@@ -313,125 +258,6 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
}
}
public Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, ref readonly LocalKeywordSet keywords)
{
ObjectDisposedException.ThrowIf(_disposed, this);
string[] fullDefines;
var totalDefineCount = descriptor.defines.Length + additionalConfig.defines.Length;
if (totalDefineCount == 0)
{
fullDefines = Array.Empty<string>();
}
else
{
fullDefines = new string[totalDefineCount];
descriptor.defines.CopyTo(fullDefines);
additionalConfig.defines.CopyTo(fullDefines.AsSpan(descriptor.defines.Length));
}
Key64<ShaderCompileResult> tsResult = default;
var asCode = descriptor.amplificationShaderCode;
if (asCode.IsCreated)
{
var config = new ShaderCompilationConfig
{
defines = fullDefines,
shaderCode = asCode.code,
entryPoint = asCode.entryPoint,
stage = ShaderStage.TaskShader,
model = additionalConfig.model,
optimizeLevel = additionalConfig.optimizeLevel,
options = additionalConfig.options,
};
var result = Compile(ref config);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
tsResult = result.Value;
}
Key64<ShaderCompileResult> msResult;
var msCode = descriptor.meshShaderCode;
if (msCode.IsCreated)
{
var config = new ShaderCompilationConfig
{
defines = fullDefines,
shaderCode = msCode.code,
entryPoint = msCode.entryPoint,
stage = ShaderStage.MeshShader,
model = additionalConfig.model,
optimizeLevel = additionalConfig.optimizeLevel,
options = additionalConfig.options,
};
var result = Compile(ref config);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
msResult = result.Value;
}
else
{
return Result.Failure("Mesh shader expected.");
}
Key64<ShaderCompileResult> psResult;
var psCode = descriptor.pixelShaderCode;
if (psCode.IsCreated)
{
var config = new ShaderCompilationConfig
{
defines = fullDefines,
shaderCode = psCode.code,
entryPoint = psCode.entryPoint,
stage = ShaderStage.PixelShader,
model = additionalConfig.model,
optimizeLevel = additionalConfig.optimizeLevel,
options = additionalConfig.options,
};
var result = Compile(ref config);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
psResult = result.Value;
}
else
{
return Result.Failure("Pixel shader expected.");
}
var compiled = new GraphicsCompiledResult
{
tsResultHash = tsResult,
msResultHash = msResult,
psResultHash = psResult,
};
return compiled;
}
public Result<ShaderCompileResult, Error> GetCompiledCache(Key64<ShaderCompileResult> key)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_compiledResults.TryGetValue(key, out var compiledResult))
{
return compiledResult;
}
return Error.NotFound;
}
public void Dispose()
{
if (_disposed)
@@ -439,11 +265,6 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
return;
}
foreach (var kvp in _compiledResults)
{
kvp.Value.Dispose();
}
_compiler.Get()->Release();
_utils.Get()->Release();

View File

@@ -0,0 +1,167 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Editor.Core.Contracts;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Editor.Core.Utilities;
internal struct GraphicsCompiledResult : IDisposable
{
public UnsafeArray<byte> asResult;
public UnsafeArray<byte> msResult;
public UnsafeArray<byte> psResult;
public void Dispose()
{
asResult.Dispose();
msResult.Dispose();
psResult.Dispose();
}
}
internal static class ShaderCompilerUtility
{
private static ReadOnlySpan<string> CombineDefines(ReadOnlySpan<string> a, ReadOnlySpan<string> b)
{
ReadOnlySpan<string> combined;
if (b.Length == 0)
{
combined = a;
}
else if (a.Length == 0)
{
combined = b;
}
else
{
var combinedDefines = new string[a.Length + b.Length];
a.CopyTo(combinedDefines);
b.CopyTo(combinedDefines.AsSpan(a.Length));
combined = combinedDefines;
}
return combined;
}
public static Result<GraphicsCompiledResult> CompileShaderPass(this IShaderCompiler shaderCompiler, ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, AllocationHandle allocationHandle)
{
var fullDefines = CombineDefines(descriptor.defines, additionalConfig.defines);
var config = new ShaderCompilationConfig
{
defines = fullDefines,
model = additionalConfig.model,
optimizeLevel = additionalConfig.optimizeLevel,
options = additionalConfig.options
};
UnsafeArray<byte> asResult = default;
if (descriptor.amplificationShaderCode.IsCreated)
{
config.shaderCode = descriptor.amplificationShaderCode.code;
config.entryPoint = descriptor.amplificationShaderCode.entryPoint;
config.stage = ShaderStage.TaskShader;
var result = shaderCompiler.Compile(ref config, allocationHandle);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
asResult = result.Value;
}
UnsafeArray<byte> msResult;
if (descriptor.meshShaderCode.IsCreated)
{
config.shaderCode = descriptor.meshShaderCode.code;
config.entryPoint = descriptor.meshShaderCode.entryPoint;
config.stage = ShaderStage.MeshShader;
var result = shaderCompiler.Compile(ref config, allocationHandle);
if (result.IsFailure)
{
asResult.Dispose();
return Result.Failure(result.Message);
}
msResult = result.Value;
}
else
{
asResult.Dispose();
return Result.Failure("Mesh shader expected.");
}
UnsafeArray<byte> psResult;
if (descriptor.pixelShaderCode.IsCreated)
{
config.shaderCode = descriptor.pixelShaderCode.code;
config.entryPoint = descriptor.pixelShaderCode.entryPoint;
config.stage = ShaderStage.PixelShader;
var result = shaderCompiler.Compile(ref config, allocationHandle);
if (result.IsFailure)
{
asResult.Dispose();
msResult.Dispose();
return Result.Failure(result.Message);
}
psResult = result.Value;
}
else
{
asResult.Dispose();
msResult.Dispose();
return Result.Failure("Pixel shader expected.");
}
var compiled = new GraphicsCompiledResult
{
asResult = asResult,
msResult = msResult,
psResult = psResult,
};
return compiled;
}
public static Result<UnsafeArray<UnsafeArray<byte>>> CompileComputeShader(this IShaderCompiler shaderCompiler, ComputeShaderDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, AllocationHandle allocationHandle)
{
var fullDefines = CombineDefines(descriptor.defines, additionalConfig.defines);
var config = new ShaderCompilationConfig
{
defines = fullDefines,
model = additionalConfig.model,
optimizeLevel = additionalConfig.optimizeLevel,
options = additionalConfig.options,
stage = ShaderStage.ComputeShader,
};
var compiled = new UnsafeArray<UnsafeArray<byte>>(descriptor.shaderCodes.Length, allocationHandle);
for (int i = 0; i < descriptor.shaderCodes.Length; i++)
{
config.shaderCode = descriptor.shaderCodes[i].code;
config.entryPoint = descriptor.shaderCodes[i].entryPoint;
var result = shaderCompiler.Compile(ref config, allocationHandle);
if (result.IsFailure)
{
for (int j = 0; j < i; j++)
{
compiled[j].Dispose();
}
compiled.Dispose();
return Result.Failure(result.Message);
}
compiled[i] = result.Value;
}
return compiled;
}
}