SDSL compiler: generic instantiations are validated in the cache, loop attributes reach the SPIR-V, a shader name as qualifier is not an instance access - #3389
Conversation
…idated like any other class, and keyed apart from its template Foo<1> was served from the cache without the source-hash validation every plain class goes through, so an edit to the generic or to one of its bases was never seen again until the cache was deleted by hand: Stride.Voxels' walk is a generic, and a method added to its interface kept resolving to the interface's empty body in the game while the same sources built correctly in the test harness. The validation is now a member of IExternalShaderLoader that every loader, decorators included, must forward. And with no macros the cache file name ignored the generic arguments, so Foo<1> and Foo shared one file and a template came back as an instantiation, still carrying its generic parameters. Regression test over a real on-disk cache. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 7918de5)
A stage method calling a static utility (LuminanceUtils.Luma(x) - every post effect does it from Shading()) was flagged as referencing non-stage members, which forces the whole shader to be imported at root level when used in a composition. The flag came from the qualifier identifier: resolving `LuminanceUtils` materializes an implicit-this member access, and the tracking saw a non-stage variable read. But the identifier reads no instance state of its own, and the member access it qualifies is the method call's concern - which already exempts qualified calls. Skip shader-kind symbols in the variable tracking. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 699d0a2)
A loop attribute written in SDSL was parsed and dropped, so every loop went to the backend without a loop control and the HLSL compiler was free to unroll it; on a march loop with texture fetches inside that is a compile measured in minutes. The attribute is now carried through to the OpLoopMerge control mask. (cherry picked from commit 6bd06e2, without its Stride.Voxels part) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Rebased on master, comments trimmed following xen2's remark on #3388 (1 to 2 lines, no history). No other change. |
9383f72 to
108d63f
Compare
| var sanitized = SanitizeName(name); | ||
| var macrosKey = defines.Length == 0 ? "default" : ComputeCacheFilename(generics, defines); | ||
| // Generic arguments are part of the key even without macros, otherwise Foo<1> and Foo share a file. | ||
| var macrosKey = defines.Length == 0 && generics == null ? "default" : ComputeCacheFilename(generics, defines); |
There was a problem hiding this comment.
You should also bump CacheFormatVersion = 2 for user who already have existing cache (otherwise they won't be invalidated and likely to crash).
| // This forces the shader to be fully imported at root level instead of stage-only during mixin. | ||
| if (symbol.MemberAccessWithImplicitThis != null && !symbol.Id.IsStage && builder.CurrentFunction is { IsStage: true }) | ||
| // A shader name used as qualifier (LuminanceUtils.Luma(x)) reads no instance state and is not tracked. | ||
| if (symbol.MemberAccessWithImplicitThis != null && !symbol.Id.IsStage && symbol.Id.Kind != SymbolKind.Shader |
There was a problem hiding this comment.
This check used to fire on the qualifier, not on the member being read. Skipping the qualifier
means nothing is left to flag this case:
shader Base { float value; } // non-stage
shader Derived : Base
{
stage float Compute() { return Base.value; } // flagged before, not anymore
}
Base.value is a real non-stage instance read, but it is compiled by the ShaderSymbol case in
AccessorChainExpression, which calls EmitSymbol directly and never goes through
CompileSymbol. So it was never tracked on its own; the qualifier was doing it by accident.
Over-flagging (the LuminanceUtils case) only costs a full import. Under-flagging can give a
stage-only import where the member is missing.
That accessor case already has field.ResolvedSymbol, so it can test IsStage on the member
itself.
| { | ||
| // Double-check cache | ||
| if (cache.TryLoadFromCache(className, genericArguments, macrosArray, out var buf, out var h)) | ||
| if (cache.TryLoadFromCache(className, genericArguments, macrosArray, out var buf, out var h) && shaderLoader.IsCachedBufferCurrent(buf)) |
There was a problem hiding this comment.
It doesn't work completely.
I have a follow up commit that cover this, I will add it to the PR.
|
I have added 2 commits, esp. to focus on the
|
PR Details
Three independent fixes to the new SDSL compiler.
An instantiated generic read back from the disk cache is validated like any other class, and keyed apart from its template
Foo<1>was served from the on-disk shader cache without the source-hash validation every plain class goes through, so an edit to the generic or to one of its bases was never seen again until the cache was deleted by hand. The validation is now a member ofIExternalShaderLoaderthat every loader, decorators included, forwards. With no macros the cache file name also ignored the generic arguments, soFoo<1>andFooshared one file and a template came back as an instantiation, still carrying its generic parameters. Regression test over a real on-disk cache.[loop]and[unroll]reach the SPIR-VA loop attribute written in SDSL was parsed and dropped, so every loop reached the backend without a loop control and FXC was free to unroll any loop whose trip count it could see; a march loop with texture fetches inside compiled in minutes. The attribute is now carried to
OpLoopMerge's control mask, which SPIRV-Cross turns back into the HLSL attribute.A shader name used as a qualifier is not an instance access
A
stagemethod calling a static utility by its shader name -LuminanceUtils.Luma(x)fromShading(), which every post effect does - was flagged as referencing non-stage members, which forces the whole shader to be imported at root level when used in a composition. The qualifier reads no instance state; shader-kind symbols are skipped by the variable tracking.Related
Independent of #3382; the generic cache fix is the one that made #3382's own branch look stale on a second machine.