Skip to content

Fix bug in matrix specialization - #12840

Open
fknfilewalker wants to merge 4 commits into
shader-slang:masterfrom
fknfilewalker:matrix-layout-mode-enum
Open

Fix bug in matrix specialization#12840
fknfilewalker wants to merge 4 commits into
shader-slang:masterfrom
fknfilewalker:matrix-layout-mode-enum

Conversation

@fknfilewalker

Copy link
Copy Markdown
Contributor

When a shader writes float4x4 without saying row_major or column_major, the layout stays unresolved until a later pass fills in the target's default. If that unresolved layout is handed to a generic first, specialization bakes it in and builds a matrix type the pass has already run past, so it never gets fixed.

The result is two versions of what should be one type reaching code generation: same name, different layout. Mixing them produces SPIR-V that fails validation.

This makes the layout get resolved before specialization can copy it around, so only one version is ever created.

A matrix type whose layout the source did not specify carries a sentinel,
SLANG_MATRIX_LAYOUT_MODE_UNKNOWN, until a target-specific pass fills in the
default. The sentinel was a plain `int`, so once it was passed as a generic
argument nothing told it apart from the row and column counts beside it:

    specialize(matrixIDifferentiableWitness, Real, 4 : Int, 4 : Int, 0 : Int, witness)

specializeMatrixLayout could therefore only resolve layouts written on a matrix
type, where operand position identifies them. Specialization then substituted
the sentinel into the generic's `matrix<T, N, M, L>` and minted a fresh
unspecified-layout matrix type after the pass had already run. That reached emit
as a second, identically named _MatrixStorage struct, and an OpPtrAccessChain
mixing the two fails spirv-val.

Declare MatrixLayoutMode in the core module and use it for the layout parameter
of `matrix`. The sentinel now carries its own type wherever it travels, so one
run of the pass, before specialization, resolves it both on matrix types and in
generic arguments, and nothing downstream can reintroduce it. The pass has to
run before lowerEnumType as well, which erases MatrixLayoutMode back to `int`;
after that point the check would degenerate to "any `int` equal to 0" and would
rewrite unrelated generic arguments.

Breaking change: code that declares the layout parameter as `let L : int`, as in
`extension<T, let R : int, let C : int, let L : int> matrix<T, R, C, L>`, must
now write `let L : MatrixLayoutMode`.
@fknfilewalker
fknfilewalker requested a review from a team as a code owner August 30, 2026 06:37
@fknfilewalker
fknfilewalker requested review from bmillsNV and a lite review from Copilot and removed request for a team August 30, 2026 06:37
@fknfilewalker fknfilewalker added the pr: breaking change PRs with breaking changes label Aug 30, 2026
@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Team

Run ID: d77a879c-51db-48bb-abbe-913b5e0505ae

📥 Commits

Reviewing files that changed from the base of the PR and between 8453258 and d4c3cd8.

📒 Files selected for processing (1)
  • source/slang/slang-ir-specialize-matrix-layout.cpp

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Changes

The matrix layout generic parameter now uses the public MatrixLayoutMode enum. Compiler paths preserve the enum type when constructing layout values. Matrix layout specialization resolves Unknown layouts before enum lowering. New SPIR-V tests cover specialization deduplication and late module specialization.

Changes

Matrix Layout Handling

Layer / File(s) Summary
MatrixLayoutMode contracts
source/slang/core.meta.slang, source/slang/diff.meta.slang, source/slang/hlsl.meta.slang
Adds MatrixLayoutMode and applies it to matrix declarations, extensions, operators, conversions, and helper functions.
Typed layout value construction
source/slang/slang-check-decl.cpp, source/slang/slang-check-expr.cpp, source/slang/slang-emit-spirv.cpp
Constructs layout values with the matrix layout type.
Unknown layout specialization
source/slang/slang-code-gen.h, source/slang/slang-emit.cpp, source/slang/slang-ir-specialize-matrix-layout.cpp
Tracks unresolved layouts, runs specializeMatrixLayout before enum lowering, and rewrites unknown layout arguments.
SPIR-V specialization regression coverage
tests/spirv/matrix-layout-dedup-specialization.slang, tests/spirv/matrix-layout-late-specialize-module.slang, tests/spirv/matrix-layout-late-specialize.slang
Adds coverage for specialization deduplication and late specialization of imported differentiable matrix code.

Merge Risk: 🔵 Low · up to d4c3c

This change resolves unknown matrix layouts before specialization and adds SPIR-V regression coverage, but the public enum naming concern and validator-coverage concern remain open. These are low merge-readiness risks that should be acknowledged or addressed.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the main change: fixing a matrix specialization bug.
Description check ✅ Passed The description directly explains the unresolved matrix layout issue, its impact on specialization and SPIR-V validation, and the implemented fix.
  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

specializeMatrixLayout rewrites IR instructions via replaceUsesWith() but does not remove the replaced IRMatrixType/IRSpecialize instructions, leaving dead IR that can increase later-pass work and risks unintended processing during full-module scans.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes a SPIR-V miscompile/validation failure caused by specializing generics while matrix layout is still unresolved (default float4x4 without row_major/column_major), which could previously lead to two structurally different but identically named matrix storage types reaching code generation.

Changes:

  • Resolve unspecified matrix layout earlier in the IR pipeline (before specialization) and also rewrite specialize(...) instructions that pass an unresolved layout as a generic argument.
  • Make matrix layout a distinct MatrixLayoutMode enum type (instead of int) so unresolved layout arguments remain recognizable during specialization.
  • Add SPIR-V regression tests to ensure specialization/layout resolution does not produce duplicate emitted types (disassembler _0 suffix).
File summaries
File Description
tests/spirv/matrix-layout-late-specialize.slang New regression test covering late specialization via imported module + conformance.
tests/spirv/matrix-layout-late-specialize-module.slang Helper module to force lazy cloning/specialization timing that previously triggered the bug.
tests/spirv/matrix-layout-dedup-specialization.slang New regression test ensuring row_major float4x4 and float4x4 dedup under -matrix-layout-row-major.
source/slang/slang-ir-specialize-matrix-layout.cpp Enhances layout specialization to also rewrite specialize args carrying Unknown layout.
source/slang/slang-emit.cpp Moves specializeMatrixLayout earlier (before specialization + before enum lowering).
source/slang/slang-emit-spirv.cpp Preserves the matrix-layout operand type when creating normalized debug matrix types.
source/slang/slang-check-expr.cpp Uses the matrix layout’s semantic type for row/column-major modifiers (supports enum-typed layout).
source/slang/slang-check-decl.cpp Same as above for layout modifiers on declarations.
source/slang/hlsl.meta.slang Updates matrix-related generics to use MatrixLayoutMode for layout template parameters.
source/slang/diff.meta.slang Updates matrix overload generic parameters to use MatrixLayoutMode.
source/slang/core.meta.slang Introduces public MatrixLayoutMode enum and updates matrix<..., L> to use it.
Review details

Suppressed comments (1)

source/slang/slang-ir-specialize-matrix-layout.cpp:110

  • After rewriting a specialize instruction with resolved layout arguments, the old IRSpecialize remains in the IR (now dead). Removing it after replaceUsesWith() matches the pattern used elsewhere (e.g. slang-ir-specialize.cpp) and keeps later full-module scans from doing unnecessary work.
        builder.setInsertBefore(specializeInst);
        auto replacement = builder.emitSpecializeInst(
            specializeInst->getFullType(),
            specializeInst->getBase(),
            (UInt)args.getCount(),
            args.getBuffer());
        specializeInst->replaceUsesWith(replacement);
  • Files reviewed: 11/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

matrixType->getColumnCount(),
builder.getIntValue(builder.getIntType(), defaultLayout));
resolvedLayout);
matrixType->replaceUsesWith(replacementMatrixType);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fknfilewalker this looks like a right way to avoid a leak.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b45acdd7-68a1-4bac-9451-aacfbc9a1140

📥 Commits

Reviewing files that changed from the base of the PR and between 28c755b and a3baca0.

📒 Files selected for processing (11)
  • source/slang/core.meta.slang
  • source/slang/diff.meta.slang
  • source/slang/hlsl.meta.slang
  • source/slang/slang-check-decl.cpp
  • source/slang/slang-check-expr.cpp
  • source/slang/slang-emit-spirv.cpp
  • source/slang/slang-emit.cpp
  • source/slang/slang-ir-specialize-matrix-layout.cpp
  • tests/spirv/matrix-layout-dedup-specialization.slang
  • tests/spirv/matrix-layout-late-specialize-module.slang
  • tests/spirv/matrix-layout-late-specialize.slang

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread source/slang/core.meta.slang
Comment thread tests/spirv/matrix-layout-dedup-specialization.slang
github-actions[bot]

This comment was marked as outdated.

@jkwak-work jkwak-work left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, although I have two trivial comments.

Comment thread source/slang/slang-ir-specialize-matrix-layout.cpp
Comment thread source/slang/slang-emit.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Team

Run ID: be671ede-1655-4c07-b779-dda74f8ddd2b

📥 Commits

Reviewing files that changed from the base of the PR and between a3baca0 and 3e977df.

📒 Files selected for processing (3)
  • source/slang/slang-code-gen.h
  • source/slang/slang-emit.cpp
  • source/slang/slang-ir-specialize-matrix-layout.cpp

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread source/slang/slang-emit.cpp
Comment thread source/slang/slang-emit.cpp
Comment thread source/slang/slang-ir-specialize-matrix-layout.cpp
Comment thread source/slang/slang-emit.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
source/slang/slang-ir-specialize-matrix-layout.cpp (1)

81-83: 🎯 Functional Correctness | 🟠 Major

Reuse the target-specific default matrix layout.

getMatrixLayoutMode() is not the complete lowering default. DefaultBufferElementTypeLoweringPolicy forces row-major for CPU, CUDA, and Metal, even when the option set requests column-major. This pass now resolves Unknown before that policy runs, so an unspecified generic matrix can become column-major and produce a non-default storage representation on those targets. Reuse the canonical target-aware default calculation before constructing resolvedLayout.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Team

Run ID: c638a443-afee-42b9-a589-1412248efef8

📥 Commits

Reviewing files that changed from the base of the PR and between 3e977df and 38790ef.

📒 Files selected for processing (2)
  • source/slang/slang-emit.cpp
  • source/slang/slang-ir-specialize-matrix-layout.cpp

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

github-actions[bot]

This comment was marked as outdated.

jkwak-work
jkwak-work previously approved these changes Sep 2, 2026

@jkwak-work jkwak-work left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Team

Run ID: e6564b68-b3a8-4e09-aae2-2fb416d0ae51

📥 Commits

Reviewing files that changed from the base of the PR and between 53e0e2b and 8453258.

📒 Files selected for processing (12)
  • source/slang/core.meta.slang
  • source/slang/diff.meta.slang
  • source/slang/hlsl.meta.slang
  • source/slang/slang-check-decl.cpp
  • source/slang/slang-check-expr.cpp
  • source/slang/slang-code-gen.h
  • source/slang/slang-emit-spirv.cpp
  • source/slang/slang-emit.cpp
  • source/slang/slang-ir-specialize-matrix-layout.cpp
  • tests/spirv/matrix-layout-dedup-specialization.slang
  • tests/spirv/matrix-layout-late-specialize-module.slang
  • tests/spirv/matrix-layout-late-specialize.slang

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread tests/spirv/matrix-layout-late-specialize.slang
github-actions[bot]

This comment was marked as outdated.

@jkwak-work

Copy link
Copy Markdown
Collaborator

@nv-slang-bot it seems like the CI test failure for "SlangPy Tests" is legit.
Can you investigate and suggest how to fix it?

@nv-slang-bot

nv-slang-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@jkwak-work Investigated — the failure is legit, and it's exactly the downstream break you'd expect from retyping the matrix layout parameter int → MatrixLayoutMode. It is not flake, and it is unrelated to the open column-major-default concern (that one is a codegen-time issue; this fails at front-end import, before any layout resolution runs).

Root cause

SlangPy declares its own generic extensions over the built-in matrix<T, R, C, L> with the layout parameter typed int. Now that this PR retypes matrix's 4th generic parameter to MatrixLayoutMode, passing an int-typed L into that slot no longer type-checks (slangpy run 33682618031):

error[E30019]: type mismatch in expression
   --> sgl/device/print.slang:261:27
261 | extension matrix<T, R, C, L> : IPrintable {
    |                           ^ expected an expression of type 'MatrixLayoutMode', got 'int'
note: explicit conversion from 'int' to 'MatrixLayoutMode' is possible

Slang applies no implicit int → MatrixLayoutMode conversion for generic value arguments, so the imports fail and cascade into every dependent module. All failing cases on both the Windows and Linux build-pr jobs trace to this one error — device/shader (test_shader.cpp), func/tensor (test_tensor.cpp), and refl/lookup (test_lookup.cpp, which fails to load the slangpy module itself).

A code search across shader-slang/slangpy finds exactly two affected sites (both hit by the failures above):

  1. slangpy/slang/staticarray.slang:10
  2. src/sgl/device/print.slang:260 (the __generic<…> line above the matrix extension)

Suggested fix (recommended)

Type the layout parameter as MatrixLayoutMode at both sites:

- public extension<T, let R : int, let C : int, let L : int> matrix<T, R, C, L> : ISizedArray<vector<T, C>, R> {}
+ public extension<T, let R : int, let C : int, let L : MatrixLayoutMode> matrix<T, R, C, L> : ISizedArray<vector<T, C>, R> {}
- __generic<T : IPrintable, let R : int, let C : int, let L : int>
+ __generic<T : IPrintable, let R : int, let C : int, let L : MatrixLayoutMode>
  extension matrix<T, R, C, L> : IPrintable {

This has to land as a companion PR in shader-slang/slangpy, and there's an ordering wrinkle worth calling out: the trigger-slangpy-tests dispatch builds SlangPy's default branch (currently at Slang 2026.16.1) against this PR's Slang, so the check will stay red until SlangPy's own sources carry the retype — but that SlangPy change references MatrixLayoutMode and so can only compile against a Slang that already defines it (i.e. once this PR is in). That's a chicken-and-egg that resolves the usual way for a coordinated breaking change: merge this PR with the "SlangPy Tests" signal acknowledged as expected-red, then merge the companion SlangPy PR (bumping SlangPy to a Slang that includes this change) to turn it green — or whatever equivalent waiver/sequencing you normally use. Just flagging that this check won't go green on #12840 by itself.

One trade-off worth a decision

let L : int for the matrix layout parameter is exactly the idiom the core module itself used before this PR, so this breaks any external Slang code that supplies an int as the 4th matrix argument — a generic parameter declared let L : int, or a raw int / the kRowMajorMatrixLayout / kColumnMajorMatrixLayout static const int constants passed positionally — not just SlangPy. That is consistent with the pr: breaking change label, and updating consumers is a legitimate choice.

If you'd rather reduce that external breakage, one option to explore is permitting an implicit int → MatrixLayoutMode conversion for the layout argument (the compiler already recognizes the explicit one, per the note above). I'd flag it as a design question, not a drop-in: a compatibility declaration would keep let L : int, and this PR's specializeMatrixLayout pass now recognizes an unresolved layout only when the literal is typed MatrixLayoutMode (isUnknownMatrixLayout checks getFullType() == matrixLayoutModeType). So the implicit route would have to guarantee — with a regression test — that specializing such an int-typed generic parameter cannot reintroduce an unrecognized unresolved-layout matrix after the pass has run. Because it touches exactly the path this PR fixes, the straightforward retype above is the safer recommendation.

I'm happy to open the companion slangpy PR with the two edits above if that's useful.

🤖 Generated by an automated Slang coworker — may be inaccurate. A human maintainer should verify.

@jkwak-work

Copy link
Copy Markdown
Collaborator

It appears this this PR will be a breaking change due to the changed type from int to MatrixLayoutMode.

@fknfilewalker , Maybe we should keep the type as int and avoid the backward-compatibility breakage?

@fknfilewalker

Copy link
Copy Markdown
Contributor Author

@fknfilewalker , Maybe we should keep the type as int and avoid the backward-compatibility breakage?

@jkwak-work the fix only works with the enum, otherwise the information gets lost.

@jkwak-work

Copy link
Copy Markdown
Collaborator

@nv-slang-bot make a PR on slangpy repo that can pass the test with this PR.

@nv-slang-bot

nv-slang-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@jkwak-work — companion SlangPy PR opened (draft): shader-slang/slangpy#1135.

It retypes SlangPy's two generic matrix extensions' layout param let L : intlet L : MatrixLayoutMode (slangpy/slang/staticarray.slang, src/sgl/device/print.slang) — the exact E30019: expected 'MatrixLayoutMode', got 'int' break this PR introduces. Full-tree scan confirms these are the only two affected sites.

⚠️ Merge-order note — there is a cross-repo CI cycle. The "SlangPy Tests" check here builds SlangPy's default branch against this PR's Slang, so it only turns green once #1135 is on main; but #1135 cannot pass SlangPy's own CI until SlangPy's pinned Slang (SGL_SLANG_VERSION = 2026.16.1, which predates this PR) is bumped to a release containing MatrixLayoutMode. Suggested sequence:

  1. Validate Add command line option to override language file extension #1135 locally against this PR via SGL_LOCAL_SLANG (git fetch origin pull/12840/head in a Slang clone, build, then configure SlangPy with -DSGL_LOCAL_SLANG=ON).
  2. Merge this PR past its red "SlangPy Tests" (known, coordinated cross-repo break).
  3. Cut a Slang release including this PR.
  4. Land Add command line option to override language file extension #1135 with a SGL_SLANG_VERSION pin bump to that release — its own CI then goes green.

Full details in the #1135 description.

🤖 Generated by an automated SlangPy coworker — may be inaccurate. A human maintainer should verify.

@jkwak-work
jkwak-work dismissed their stale review September 4, 2026 00:22

I am gonna need to discuss with the team about how to proceed; regarding the backward-compatibility breaking aspect.
We may need to post an announcement.

@jkwak-work jkwak-work added the Office-Tess To be discussed during Tess' office hours label Sep 4, 2026
@tangent-vector

Copy link
Copy Markdown
Contributor

This is a messy problem, due in no small part to a bunch of questionable design choices we've made over time about how to handle matrix layout in Slang. It might be impossible to implement a truly Good solution to some of this without changing the language/compiler rules. I'm trying to consider the aims of this PR as-is, though, rather than try to insist on a larger refactoring fix.

At first I wasn't sure if the switch to using an explicit enum type for the matrix layout mode was a good idea, but now that I've been staring at the PR some more, I think it could actually be the way we unlock a significantly simpler version of all this logic.

The IRIntLitVal that represents the Unknown matrix layout mode is now a completely unique instruction/value (because it has a dedicated type rather than just being an int), and it should be getting deduplicated within an IRModule, using the existing Slang IR infrastructure. Those properties should mean that:

  • We can check if there is any code in a module that is still using an Unknown matrix layout mode by getting the IRInst* that represents the mode and querying if it has any uses.

  • We can switch all uses of the Unknown matrix layout mode over to our chosen target-default matrix layout mode in one step, just by using the Slang IR replaceUsesWith operation. E.g., unknownMatrixLayoutModeVal->replaceUsesWith(rowMajorLayoutModeVal). Done.

Unless I'm missing something, the simple act of making that mode into an enum should mean that the pass to apply a default matrix layout mode across an entire IR module is now basically trivial.

@jkwak-work

Copy link
Copy Markdown
Collaborator

We will proceed with the PR as it is.
But I need to sort out how to fix the slangpy test failure along with this PR.
@fknfilewalker , please be patient because it can take a few days.

@fknfilewalker

fknfilewalker commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author
  • We can check if there is any code in a module that is still using an Unknown matrix layout mode by getting the IRInst* that represents the mode and querying if it has any uses.
  • We can switch all uses of the Unknown matrix layout mode over to our chosen target-default matrix layout mode in one step, just by using the Slang IR replaceUsesWith operation. E.g., `unknownMatrixLayoutModeVal-

Good idea, I think with replaceUsesWith I can make this even simpler.

Edit: replaceUsesWith can't be used because a switch case label on MatrixLayoutMode.Unknown is lowered as the same deduplicated enum-typed constant as a defaulted matrix layout, so a blanket replace would silently rewrite that label too.

github-actions[bot]

This comment was marked as outdated.

github-actions[bot]

This comment was marked as outdated.

@fknfilewalker
fknfilewalker force-pushed the matrix-layout-mode-enum branch from 7eeaf8e to 8453258 Compare September 4, 2026 21:02
@jkwak-work

Copy link
Copy Markdown
Collaborator

FYI, I tried the following code to see if we can workaround the backward-compatibility breaking problem:

enum E : int { A = 0, B = 1 }
struct Foo<let L : E, let dummy : int> { int v; }
typealias Foo<let L : int> = Foo<(E)(L), 0>;

But generic function overloading is not implemented by a reason that the generic types/values are not considered for the lookup.

error[E30200]: conflicting declaration
 3 | typealias Foo<let L : int> = Foo<(E)(L), 0>;
   |           ^^^ declaration of 'Foo' conflicts with existing declaration

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: 🟡 Has issues — 2 gaps, 3 clarity nits

Changes the matrix<T,R,C,L> layout generic parameter from int to a new MatrixLayoutMode enum so an unresolved (Unknown) layout stays recognizable, and moves specializeMatrixLayout ahead of generic specialization and enum lowering so row_major float4x4 and float4x4 resolve to one type before they become distinct specialization keys. The traversal, SSA mechanics (dead specialize insts are DCE'd), pass reordering, and gating check out; the new field is zero-initialized (requiredLoweringPassSet = {}). The concerns are a fragile type-identity invariant in the detection path and test-matrix breadth.

Changes Overview

MatrixLayoutMode enum + core-module signatures (core.meta.slang, diff.meta.slang, hlsl.meta.slang)

  • The fourth matrix generic parameter L becomes MatrixLayoutMode (values spliced from SLANG_MATRIX_LAYOUT_*) instead of int, across all matrix extensions/generics. Marked pr: breaking change.

Layout-type propagation at construction sites (slang-check-decl.cpp, slang-check-expr.cpp, slang-emit-spirv.cpp)

  • Build the layout IntVal/IRIntLit with the matrix's actual layout type (getLayout()->getType() / getFullType()) rather than a bare int type, so the operand carries MatrixLayoutMode.

Pass rewrite + reordering (slang-ir-specialize-matrix-layout.cpp, slang-emit.cpp, slang-code-gen.h)

  • specializeMatrixLayout now collects both unknown-layout matrix types and specialize insts that pass Unknown as a generic argument, resolving both to the target default; runs before specialization/enum-lowering, gated by a new unresolvedMatrixLayout flag in calcRequiredLoweringPassSet.

Tests (tests/spirv/matrix-layout-dedup-specialization.slang, matrix-layout-late-specialize.slang, matrix-layout-late-specialize-module.slang)

  • Cover the direct-matrix-type dedup path and the late/generic-specialization path; both assert a single storage/struct type via positive-anchor + -NOT _0.
Findings (5 total)
Severity Location Finding
🟡 Gap slang-ir-specialize-matrix-layout.cpp:42 Unknown-layout detection keys on IR type identity; comment says "silently skipped" but debug asserts, and a plain-int Unknown literal (older serialized module) is silently skipped in release, reintroducing the duplicate-storage bug.
🟡 Gap tests/spirv/matrix-layout-dedup-specialization.slang:1 Only row-major default and SPIR-V target tested; column-major default and non-SPIR-V backends (both affected) uncovered.
🟡 Nit core.meta.slang:2303 New MatrixLayoutMode enum duplicates adjacent k*MatrixLayout int constants with no note on the relationship.
🟡 Nit slang-emit.cpp:650 as<IRMatrixType>(inst) inside case kIROp_MatrixType guards an impossible shape; prefer cast<>.
🟡 Nit slang-ir-specialize-matrix-layout.cpp:53 visitSpecializeInsts ordering precondition documented but not asserted.

reviewed: 8453258 · diff sha256 8bfe93b51c2c

auto layout = matrixType->getLayout();
if (!matrixLayoutModeType)
matrixLayoutModeType = layout->getFullType();
SLANG_ASSERT(layout->getFullType() == matrixLayoutModeType);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: unknown-layout detection keys on IR type identity, with a fail-silent release path

isUnknownMatrixLayout recognizes an unknown layout only when the literal's type is identical to the one MatrixLayoutMode instance captured from the first matrix seen:

auto lit = as<IRIntLit>(inst);
if (!lit || lit->getFullType() != matrixLayoutModeType)   // line 17
    return false;
return lit->getValue() == SLANG_MATRIX_LAYOUT_MODE_UNKNOWN;

and visitMatrixTypes enforces that all matrix layout operands share that one type:

SLANG_ASSERT(layout->getFullType() == matrixLayoutModeType);   // line 42

Two problems:

  1. The comment contradicts the code. The preceding comment says a mismatch "would be silently skipped," but SLANG_ASSERT fires in debug — it is silent only in release (where the assert is compiled out and the getFullType() check in isUnknownMatrixLayout drops the literal). Please make the comment describe the actual dual behavior.

  2. A plain-int-typed Unknown layout literal (value 0) would be silently skipped in release, leaving that matrix unresolved and lowering it to a second, identically-named storage type — exactly the duplicate-_MatrixStorage bug this PR fixes. Before this PR the layout operand was plain int, and k_maxSupportedModuleVersion/k_minSupportedModuleVersion (slang-ir.h:2260-2261) were not bumped, so a .slang-module serialized by an older compiler can still deserialize with an int-typed layout literal. Linking such a module alongside a fresh MatrixLayoutMode-typed compile puts both spellings of Unknown in one module.

Note the type check is intentional — it distinguishes an enum-typed Unknown (value 0) from an unrelated int generic argument of value 0 (e.g. Bar<0>), so simply ignoring the type is not the fix. Please either (a) confirm that a plain-int Unknown matrix-layout literal can never reach this pass (state the invariant and make it a SLANG_RELEASE_ASSERT rather than a debug-only assert + silent release skip), or (b) normalize the layout literal's type on deserialization/linking so the identity check holds.

Example: compile A.slang (uses float4x4) to a module with a pre-this-PR compiler → its layout literal is int-typed. import "A" from a new-compiler shader that also uses float4x4. In release, A's matrix stays Unknown and emits a duplicate storage struct; in debug the assert aborts.

@@ -0,0 +1,25 @@
//TEST:SIMPLE(filecheck=CHECK): -target spirv-asm -entry main -stage compute -matrix-layout-row-major

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: only the row-major default and the SPIR-V target are exercised

Both new tests pin -matrix-layout-row-major:

//TEST:SIMPLE(filecheck=CHECK): -target spirv-asm -entry main -stage compute -matrix-layout-row-major

The rewritten pass resolves every unknown layout to resolvedLayout = builder.getIntValue(collector.matrixLayoutModeType, defaultLayout) where defaultLayout = target->getOptionSet().getMatrixLayoutMode(). Two behaviors that this PR changes are never covered:

  • Column-major default. Column-major is the more common Vulkan/SPIR-V default, and it flows a different value through both resolvedLayout and the new specialize-inst rebuild. A bug that hard-codes/mishandles row-major would still pass CI.
  • Non-SPIR-V targets. The dedup and the pass-ordering move happen in linkAndOptimizeIR for all backends (a Wrapper<row_major float4x4> / Wrapper<float4x4> pair specializes once for HLSL/GLSL/CPU too), but all three tests are SPIR-V-only.

Suggestion: add a -matrix-layout-column-major variant of the dedup test asserting the two spellings still collapse to one struct (same positive-anchor + -NOT _0 pattern), and consider one non-SPIR-V (e.g. -target hlsl or CPU compute) variant so the target-independent fix is exercised outside SPIR-V.

/// @remarks `Unknown` takes the order selected for the target. This is an enum rather than an
/// `int` so an unresolved layout stays recognizable when passed as a generic argument.
/// @category math_types Math types
enum MatrixLayoutMode : int

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: new MatrixLayoutMode enum duplicates the adjacent k*MatrixLayout constants without noting the relationship

MatrixLayoutMode.RowMajor/.ColumnMajor now carry the same values as kRowMajorMatrixLayout/kColumnMajorMatrixLayout immediately above (lines 2295-2296). Two spellings of the same values now coexist with no comment on which callers should use, or whether the k* constants are kept only for source compatibility.

Suggestion: add a one-line note stating the relationship (and whether the k* constants are now superseded/retained for compatibility) so a future maintainer doesn't have to guess whether they are redundant.

case kIROp_MatrixType:
// An `Unknown` layout needs the pass. So does `Unknown` passed as a generic argument,
// which this scan cannot recognize, so a generic (non-literal) layout requests it too.
if (auto matrixType = as<IRMatrixType>(inst))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Nit: as<IRMatrixType> inside case kIROp_MatrixType guards an impossible shape

case kIROp_MatrixType:
    ...
    if (auto matrixType = as<IRMatrixType>(inst))

Within case kIROp_MatrixType, inst is already known to be an IRMatrixType, so this as<> null-check can never fail — it reads as defensive handling of an impossible shape. Per CLAUDE.md ("fail loudly on out-of-contract input / don't guard impossible shapes"), an unconditional cast<IRMatrixType>(inst) states the invariant more clearly than an if whose false branch is dead.


// Collects the `specialize` insts that pass `Unknown` as a generic argument.
// Needs `matrixLayoutModeType`, so call after `visitMatrixTypes`.
void visitSpecializeInsts(IRInst* parent)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Nit: visitSpecializeInsts' ordering precondition is documented but not enforced

// Needs `matrixLayoutModeType`, so call after `visitMatrixTypes`.
void visitSpecializeInsts(IRInst* parent)

If matrixLayoutModeType is still null (i.e. visitMatrixTypes hasn't run), every isUnknownMatrixLayout call returns false and this walk silently collects nothing — a reordering yields a silent miss rather than a failure. The single caller orders the two correctly and early-returns on a null type, so this is not a live bug, but consider a SLANG_ASSERT(matrixLayoutModeType) at entry so the internal contract can't be violated silently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Office-Tess To be discussed during Tess' office hours pr: breaking change PRs with breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants