Skip to content

Capabilities: error on capabilities incompatible with compilation target (fixes #4422) - #11225

Open
zangold-nv wants to merge 17 commits into
shader-slang:masterfrom
zangold-nv:gh-4422
Open

Capabilities: error on capabilities incompatible with compilation target (fixes #4422)#11225
zangold-nv wants to merge 17 commits into
shader-slang:masterfrom
zangold-nv:gh-4422

Conversation

@zangold-nv

Copy link
Copy Markdown
Contributor

Summary

Previously, passing a capability flag that is incompatible with the
compilation target (e.g. -capability spirv_1_5 -target hlsl) was
silently ignored. This PR detects that mismatch and emits an error.

  • New error E36121 requested capability 'X' is incompatible with compilation target 'Y' — fires when an explicitly requested capability (via -capability) is incompatible with the code-gen target family (e.g. SPIRV cap on HLSL target, GLSL cap on CPP target)
  • E36112 promoted from warning to error — profile/stage mismatch (e.g. -entry vsmain -profile ps_6_0) now produces an error instead of a warning
  • -ignore-capabilities suppresses E36121, consistent with all other capability diagnostics
  • GLSL targets are exempt for SPIRV-family capabilities, which are auto-converted to glsl_spirv_* equivalents rather than rejected
  • Cross-target version aliases (e.g. sm_6_0, which spans both HLSL and SPIRV) are not flagged — they are valid on any of their target families

Implementation notes

  • New TargetRequest::checkCapabilities(DiagnosticSink*) method, called once per target in FrontEndCompileRequest::checkEntryPoints()
  • Private TargetRequest::isGLSLBasedTarget() helper shared by getTargetCaps() and checkCapabilities() to avoid duplicating the GLSL-target detection logic
  • CapabilityTargetSets::containsKey(CapabilityAtom::spirv) used for the GLSL exemption check (robust against unordered-map iteration order)

Test plan

  • tests/language-feature/capability/incompatible-capability-for-target.slang (new)
    • SPIRV cap on HLSL target → E36121 with capability name in message
    • SPIRV cap on SPIRV target → no error, SPIRV output present
    • SPIRV cap + -ignore-capabilities → suppressed
    • SPIRV cap on GLSL target → auto-converted, no error
    • HLSL cap on GLSL target → E36121
    • CPP target + GLSL-family cap → E36121
  • conflicting-profile-stage-for-entry-point.slang: updated for E36112 promotion
  • specializeTargetSwitch.slang: removed stale -capability image_loadstore from the CPP test case that now correctly triggers E36121

Fixes #4422

@zangold-nv zangold-nv self-assigned this May 21, 2026
@zangold-nv
zangold-nv requested a review from a team as a code owner May 21, 2026 01:14
@zangold-nv
zangold-nv requested review from bmillsNV and removed request for a team May 21, 2026 01:14
@zangold-nv zangold-nv added the pr: breaking change PRs with breaking changes label May 21, 2026
@coderabbitai

coderabbitai Bot commented May 21, 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
📝 Walkthrough

Walkthrough

Adds target capability checks before entry-point validation. The change defines E36121, promotes E36112 to an error, adds GLSL-based target classification, integrates per-target validation, and expands capability regression tests.

Changes

Capability compatibility validation against compilation targets

Layer / File(s) Summary
Capability diagnostics and TargetRequest API
source/slang/slang-diagnostics.lua, source/slang/slang-target.h
Defines E36121, changes E36112 from a warning to an error, and adds the TargetRequest capability validation API.
Target capability validation implementation
source/slang/slang-target.cpp
Classifies GLSL-based targets and validates explicit capability requests against cooked target capabilities.
Compiler capability check integration
source/slang/slang-check-shader.cpp
checkEntryPoints validates each target before entry-point validation.
Capability compatibility regression coverage
tests/language-feature/capability/*, tools/slang-unit-test/unit-test-session-capability-not-flagged.cpp
Covers incompatible, compatible, suppressed, cross-target, session-level, and target-level capability requests. It also updates related diagnostic and target invocation tests.

Suggested reviewers: bmillsnv, jkwak-work

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: error reporting for capabilities incompatible with the compilation target.
Description check ✅ Passed The description directly explains incompatible capability errors, profile-stage errors, implementation changes, and related tests.
Linked Issues check ✅ Passed The changes satisfy issue #4422 by reporting incompatible target capabilities and profile-stage combinations as errors.
Out of Scope Changes check ✅ Passed The implementation, diagnostic updates, and regression tests are directly related to the capability validation objectives in issue #4422.

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.

@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

Run ID: 23be7610-ffd4-4976-835d-148a299e21e7

📥 Commits

Reviewing files that changed from the base of the PR and between 38411f6 and 42383d5.

📒 Files selected for processing (7)
  • source/slang/slang-check-shader.cpp
  • source/slang/slang-diagnostics.lua
  • source/slang/slang-target.cpp
  • source/slang/slang-target.h
  • tests/language-feature/capability/conflicting-profile-stage-for-entry-point.slang
  • tests/language-feature/capability/incompatible-capability-for-target.slang
  • tests/language-feature/capability/specializeTargetSwitch.slang

Comment thread source/slang/slang-target.cpp Outdated
Comment thread tests/language-feature/capability/specializeTargetSwitch.slang Outdated
github-actions[bot]

This comment was marked as outdated.

@zangold-nv
zangold-nv marked this pull request as draft June 2, 2026 14:57
zangold-nv and others added 5 commits August 2, 2026 21:20
…shader-slang#4422)

Two validation gaps are addressed:

1. Requesting a capability whose target family conflicts with the
   code-gen target (e.g. `-capability spirv_1_5 -target hlsl`) now
   emits error E36121 instead of silently ignoring the capability.
   The new `TargetRequest::checkCapabilities(DiagnosticSink*)` method
   is called once per target from `FrontEndCompileRequest::checkEntryPoints`.
   GLSL targets are exempt for SPIRV-family capabilities since those
   are intentionally auto-converted to glsl_spirv equivalents.

2. Promoting the stage-vs-profile mismatch diagnostic
   (entry-point-and-profile-are-incompatible, E36112) from a warning
   to an error, so that e.g. compiling with profile `cs_5_0` while
   the entry point is a fragment shader is now a hard error.

Tests:
- New test: tests/language-feature/capability/incompatible-capability-for-target.slang
- Updated: conflicting-profile-stage-for-entry-point.slang (warning → error)
- Updated: specializeTargetSwitch.slang (remove now-invalid mismatched capability)
- Use containsKey(CapabilityAtom::spirv) instead of getCompileTarget()==spirv
  in the GLSL exemption check, to avoid relying on unordered dictionary
  iteration order for capabilities spanning multiple target families.
- Add sync comment noting that the isGLSLTarget logic mirrors getTargetCaps().
- Expand incompatible-capability-for-target.slang to cover:
  - -ignore-capabilities suppressing E36121
  - GLSL target + spirv_1_5 exemption (should NOT error)
  - CPP target + image_loadstore (should error)
  - Document that cross-target aliases like sm_6_0 are designed to include
    SPIRV paths and intentionally do not trigger E36121.
…tic message

- Pass getLinkage()->m_optionSet instead of per-target optionSet to maybeDiagnose,
  consistent with all other capability diagnostics in slang-check-shader.cpp. This
  ensures global -ignore-capabilities suppresses E36121 as expected.

- Move requestedCap and target names into the primary diagnostic message rather than
  a location-less span, so the capability name always appears in the error output.

- Add capability name anchors to test CHECK_ERR/CHECK_ERR_CPP cases, and positive
  anchors (result code / OpEntryPoint) to the success -NOT cases.

- Update slang-target.h doc comment to reflect that maybeDiagnose may suppress the
  diagnostic when capability checking is disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract shared isGLSLBasedTarget() private helper on TargetRequest, eliminating
  the duplicate isGLSLTarget switch that existed in both getTargetCaps() and
  checkCapabilities(). The comment flagging the sync risk is removed together
  with the duplicate code.

- Add test case: HLSL-specific capability (_sm_6_6) on a GLSL target triggers
  E36121, covering the missing equivalence-class direction.

- Clarify comment on SPIRV success test case: -target spirv without
  -emit-spirv-directly takes the GLSL-SPIRV path internally, which is why
  SPIRV capability atoms are auto-converted rather than rejected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@zangold-nv
zangold-nv marked this pull request as ready for review August 3, 2026 02:36
github-actions[bot]

This comment was marked as outdated.

@nv-slang-bot

nv-slang-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Root cause is downstream in SlangPy — this PR is working as designed.

  • Status: SlangPy Tests is the only red on db61cec (combined status failure; license/cla + CodeRabbit green, all slang-side check-runs green). Deterministic on linux-gcc and windows-msvc across both run attempts — not a flake, and not rerunnable from the slang side. CI run: https://github.com/shader-slang/slangpy/actions/runs/30779697035
  • Link: filed as hlsl_nvapi capability requested unconditionally — breaks all non-HLSL targets under slang#11225 slangpy#1087 — the durable downstream artifact, with full evidence and the suggested patch.
  • Verdict: SlangPy requests the hlsl_nvapi capability unconditionally in SlangSession::create_session (src/sgl/device/shader.cpp:404-408) while the NVAPI module it backs is gated on SGL_HAS_NVAPI && DeviceType::d3d12 (shader.cpp:250 create, :656 link). The request is strictly broader than the link it backs — a pre-existing latent bug that older Slang tolerated silently. This PR correctly turns it into error[E36121]: requested capability 'hlsl_nvapi' is incompatible with compilation target 'spirv'. No change is needed in this PR.
  • Evidence: sgl_tests 172 pass / 28 fail / 3 skip with 0 assertions failed — all 28 are thrown Failed to load slang module … immediately followed by E36121, all in the vulkan subcase, zero d3d12 failures. Extraction method, so this is checkable rather than trust-me: doctest prints a multi-line DEEPEST SUBCASE STACK REACHED header with the subcase name on the following indented line, and windows.log is CRLF — so tr -d '\r' < log | grep -A2 'DEEPEST SUBCASE STACK REACHED' yields 28 vulkan and 0 d3d12 per job, with grep -c on the header = 28 confirming the enumeration is complete. Correction to an earlier version of this comment: I first wrote that the d3d12 subcases "passed" and called it decisive. That is an inference, not an observation — the logs contain no device-creation line, and every d3d12 string in windows.log is a build-artifact path. The honest version: tests/sgl/testing.cpp:73 iterates {d3d12, vulkan} on Windows vs {vulkan} on Linux, and Windows reports 18535 assertions against Linux's 15593 — consistent with d3d12 executing and not failing, but not direct evidence. Both platforms report identical case counts (200 / 172 / 28 / 3), so that delta could equally come from Windows-only assertions inside shared cases; D3D12 CI is what would settle it. None of this affects the diagnosis — the SGL_HAS_NVAPI ON/OFF contrast below is the load-bearing evidence. The platforms fail for two different reasons, confirming both halves of the missing predicate: linux configures SGL_HAS_NVAPI: OFF (capability requested for a module absent from the build), windows ON (requested for a non-HLSL target).
  • Next action / blocker: merge order is slangpy first. slangpy/.github/workflows/ci-latest-slang.yml checks out slangpy at its default branch for repository_dispatch runs — client_payload controls only the slang ref — so this status stays red until the guard is merged to slangpy main; an open or draft PR there will not flip it. The guard is a green no-op against released Slang (slangpy pins SGL_SLANG_VERSION 2026.12, pre-Capabilities: error on capabilities incompatible with compilation target (fixes #4422) #11225), so it can land independently and immediately. A fix is being prepared downstream; no action needed from @zangold-nv beyond waiting for that merge and a re-dispatch.

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

nv-slang-bot Bot added a commit to shader-slang/slangpy that referenced this pull request Aug 3, 2026
…nked

SlangSession::create_session requested the hlsl_nvapi Slang capability
unconditionally. The sgl NVAPI Slang module that provides it is created and
linked into programs only under SGL_HAS_NVAPI && DeviceType::d3d12, so the
capability request was strictly broader than the module backing it. (The
separate SGL_ENABLE_NVAPI compiler define, which shader code branches on, is
already gated on that same predicate.)

Slang 2026.12, the currently pinned version, tolerates the mismatch silently.
shader-slang/slang#11225 rejects it:

  error[E36121]: requested capability 'hlsl_nvapi' is incompatible with
  compilation target 'spirv'

Gate the request on the predicate already used at the module create and link
sites so they cannot drift apart. The non-d3d12 device types this function maps
to a target -- spirv, metal, wgsl, ptx and host-callable -- have no NVAPI module
linked; of those, only spirv is covered by a job in the dispatch matrix that
surfaced this, so that is the only target observed failing.

Both halves of the predicate are load-bearing. SGL_HAS_NVAPI is OFF wherever
slang-rhi does not build its NVAPI target -- which requires Windows, x86_64 and
a D3D backend -- and there the module is never created or linked. Where it is
ON, the capability is genuinely backed for d3d12 but not for other device
types, so the device check is still required.

Fixes #1087
@coderabbitai

coderabbitai Bot commented Aug 6, 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: Pro Plus

Run ID: d89ac489-0eba-4d56-9615-6de6d29472e8

📥 Commits

Reviewing files that changed from the base of the PR and between 9eb90c5 and f517148.

📒 Files selected for processing (7)
  • source/slang/slang-check-shader.cpp
  • source/slang/slang-diagnostics.lua
  • source/slang/slang-target.cpp
  • source/slang/slang-target.h
  • tests/language-feature/capability/conflicting-profile-stage-for-entry-point.slang
  • tests/language-feature/capability/incompatible-capability-for-target.slang
  • tests/language-feature/capability/specializeTargetSwitch.slang

github-actions[bot]

This comment was marked as outdated.

@nv-slang-bot

nv-slang-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Status unchanged: SlangPy Tests is still the only red, still caused downstream, and this PR still needs no change. Re-verified today against the current head f517148 (my earlier comment was against db61cec).

  • Status: SlangPy Tests = failure; license/cla and CodeRabbit green, and every non-skipped slang-side check-run passed (the only non-successes are skipped: retry-on-gpu-failure, board-sync, Claude Code Assistant). Failing run: slangpy actions/runs/31087999613, both jobs, 28 × error[E36121]: requested capability 'hlsl_nvapi' is incompatible with compilation target 'spirv' each, with the same 200 | 172 passed | 28 failed | 3 skipped summary — an identical failure signature and test totals to the earlier run.
  • Verdict: the fix is a one-hunk change in SlangPy, not here. SlangSession::create_session requested hlsl_nvapi unconditionally while the NVAPI Slang module backing it is created/linked only under SGL_HAS_NVAPI && DeviceType::d3d12. That mismatch is what this PR correctly rejects.
  • Blocker (the actual one): the SlangPy fix is written, reviewed, and verified. slangpy#1088 is approved by a SlangPy maintainer with all CI green; it remains a draft and is not merged.
  • Why the check remains blocked on SlangPy main: ci-latest-slang.yml's build-pr job checks out SlangPy with no ref: — always its default branch — while client_payload selects only the slang ref. I confirmed today that the failing run built SlangPy main at 507b4cf, and that 507b4cf still contains the unguarded call — the guard is absent from SlangPy main and present in slangpy#1088. So this check will stay red on every re-run until the guard lands on SlangPy main; no appropriate code change on this PR is needed to turn it green.
  • Next action / ETA: no code change or rebase is needed on this PR. ETA: no calendar estimate — completion depends on slangpy#1088 being merged, followed by a fresh dispatch of SlangPy Tests against this PR (it isn't bot-rerunnable, and rerunning the old event won't pick up a newer SlangPy main).

One note on the behind master state: I'm deliberately not touching it. This is your branch (gh-4422), and rebasing someone else's PR isn't mine to do — flagging only so it's visible, not as a request.

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

Session-level capabilities (set via SessionDesc.compilerOptionEntries)
are inherited by every target through CompilerOptionSet::inheritFrom().
Multi-backend frameworks like SGL/SlangPy legitimately add hlsl_nvapi at
the session level to enable it for D3D12 targets while also compiling the
same session for Vulkan/SPIRV, where hlsl_nvapi is not valid.

Previously checkCapabilities() read from targetOptionSet.getArray() which
includes inherited capabilities, and so E36121 fired incorrectly for
every SPIRV compile in an SGL session, breaking the SlangPy CI tests.

Fix: skip any capability that also appears in the linkage-level option set
(getLinkage()->m_optionSet), which holds exactly what was set at the session
level.  Capabilities set explicitly for a specific target via
TargetDesc.compilerOptionEntries or addTargetCapability() go directly into
the target's option set and are NOT in the linkage option set, so they
continue to be checked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@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

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-target.cpp (1)

67-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document TargetRequest::isGLSLBasedTarget().

Add a complete-sentence function comment. State that it classifies targets that use GLSL capability semantics. State why indirect SPIR-V uses that classification while direct SPIR-V does not.

As per coding guidelines, “Comment C++ functions as complete sentences: what, then why.”

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bf65b84c-c73a-44aa-82d7-e33b4b69065f

📥 Commits

Reviewing files that changed from the base of the PR and between f517148 and 7342e35.

📒 Files selected for processing (3)
  • source/slang/slang-target.cpp
  • tests/language-feature/capability/session-level-capability-not-flagged.slang
  • tools/slang-unit-test/unit-test-session-capability-not-flagged.cpp

Comment thread source/slang/slang-target.cpp Outdated
Comment thread tests/language-feature/capability/session-level-capability-not-flagged.slang Outdated
@nv-slang-bot

nv-slang-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Re-verified at the new head 7342e35 — my two earlier comments are now superseded: the SlangPy blocker I named is no longer what's holding this PR. Two things need your attention, one of them mechanical.

  • Status: 7342e35 ("checkCapabilities: skip capabilities inherited from the session level") fixes the SlangPy breakage slang-side, so this PR no longer depends on slangpy#1088 (still an unmerged draft, still approved). I withdraw the "no change needed here / wait for slangpy" framing of my 2026-08-03 and 2026-08-06 comments — your commit is a better fix than the one I was waiting on, because it stops slang from rejecting a legitimate multi-backend session rather than making SGL work around it.
  • ⚠️ No CI ran on 7342e35. The head has 0 check-runs and 0 workflow runs; only license/cla and CodeRabbit reported (combined status success, which is why this can read as green). For comparison the previous head f517148 had 48 check-runs / 12 workflow runs, and unrelated PRs got pull_request runs inside the same 21:30–22:30Z window, so this is specific to this push, not an Actions outage. ci.yml triggers on pull_request for master and the PR is non-draft, so it should have fired — worth a re-push or a workflow_dispatch on gh-4422, because right now no build or test has ever executed against this commit. Related: the PR is BEHIND master.
  • Verdict on the fix — sound for the case it targets, with one gap. The skip is keyed on value, not provenance: isSessionLevelCap compares each target capability against getLinkage()->m_optionSet's entries by kind + intValue/stringValue. Because Capability is in allowDuplicate() (slang-compiler-options.cpp:442), inheritFrom appends rather than replaces, so an embedder that sets the same capability at both session and target level has its target-level request silently skipped too — the error this PR exists to raise is lost for that case. A provenance-based check (test the target's own pre-inherit entries, or tag inherited values) would close it. Also note the kind equality requirement: a session-level Int capability will not suppress a String-kind target capability.
  • The CLI path is unaffected (this is a good thing, and worth stating in the PR): -capability is parsed to getCurrentTarget() (slang-options.cpp:3315, :1982-1986), which is always a RawTarget, and CompilerOptionName::Capability is never written to linkage->m_optionSet by the options parser. So sessionCapArray is empty for slangc, the skip never fires, and the three E36121-expecting directives in incompatible-capability-for-target.slang still pass. The skip is meaningful for API/embedder sessions only.
  • Next action — session-level-capability-not-flagged.slang does not cover the regression it is named for. Both its directives are target-level (-target hlsl … -capability spirv_1_5, and the same with -ignore-capabilities); neither reaches isSessionLevelCap, and the first duplicates the opening directive of the sibling incompatible-capability-for-target.slang. Its own header concedes this ("We cannot express 'session-level vs target-level' through slangc flags alone"). The real coverage is unit-test-session-capability-not-flagged.cpp, which does use SessionDesc vs TargetDesc correctly — but slang-unit-test is EXCLUDE_FROM_ALL, so please confirm it actually runs in CI. Suggest either dropping the .slang file as redundant or renaming it to what it tests.

I have not pushed anything to your branch and won't — gh-4422 is yours. Flagging the missing CI because a success combined status on a commit with zero check-runs is easy to read as a pass.

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

@coderabbitai

coderabbitai Bot commented Aug 7, 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: Pro Plus

Run ID: ca4ebb60-1a1f-40c4-8d77-f1a5b2d12b25

📥 Commits

Reviewing files that changed from the base of the PR and between 3241dfa and 4871b04.

📒 Files selected for processing (9)
  • source/slang/slang-check-shader.cpp
  • source/slang/slang-diagnostics.lua
  • source/slang/slang-target.cpp
  • source/slang/slang-target.h
  • tests/language-feature/capability/conflicting-profile-stage-for-entry-point.slang
  • tests/language-feature/capability/incompatible-capability-for-target.slang
  • tests/language-feature/capability/session-level-capability-not-flagged.slang
  • tests/language-feature/capability/specializeTargetSwitch.slang
  • tools/slang-unit-test/unit-test-session-capability-not-flagged.cpp

Comment thread tools/slang-unit-test/unit-test-session-capability-not-flagged.cpp
github-actions[bot]

This comment was marked as outdated.

…name in diagnostic, expand test coverage

- Extract shared `decodeCapabilityOption` static method used by both
  `getTargetCaps()` and `checkCapabilities()`, eliminating duplicated
  Int/String decode logic.
- Fix E36121 diagnostic to report the user-specified target name
  (e.g. 'spirv') rather than the capability-set's target atom (which
  returns 'glsl' on the GLSL-SPIRV pipeline), by using
  TypeTextUtil::getCompileTargetName(asExternal(getTarget())).
- Add test case for -emit-spirv-via-glsl: SPIRV cap on GLSL-SPIRV
  pipeline is auto-converted, no E36121.
- Add Metal/_sm_6_6, WGSL/spirv_1_5, CUDA/_GLSL_460 error test cases
  verifying the target name in the diagnostic.
- Fix wrong comment about -target spirv default path (it is direct emit,
  not via-GLSL).
- Add -emit-spirv-directly test confirming no false positive.
github-actions[bot]

This comment was marked as outdated.

@tangent-vector tangent-vector 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.

I have several fine-grained feedback points / change requests, but also one high-level concern about this entire PR:

I presume that we already have logic in the Slang compiler that translates the -capability options that a user specified on the command line into capability atoms that get turned into a capability set, for use downstream... but then this PR seems to introduce an entirely new code path that iterates over command-line options and handles translation of those options over to capabilities (including the string-to-integer mapping which, again, other logic would presumably already have done). Ultimately it does all of this just so that it can do an atom-by-atom iteration and check individual atoms against another capability set, but this seems like exactly the kind of operation that should be available and implemented more fundamentally for the sets themselves.

What I'd expect to see here is a stronger architectural direction, where we recognize capabilities coming from different sources, and store those capability sets with their provenance so that they can feed into downstream operations like validation. We know that capabilities can come from session-level options, target-level options, entry-point attributes, inferred requirements of an entry-point function and its call graph, etc. What we need is a coherent description for how those capability sets should be combined, and what validation should be applied in cases where something inconsistent/incoherent arises between all those different sources.

This PR seems like a clear example of an agent creating a band-aid fix, rather than addressing an actual issue in SW architecture.

Comment thread source/slang/slang-target.cpp Outdated

void TargetRequest::checkCapabilities(DiagnosticSink* sink)
{
if (!sink)

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.

This early-out should either:

  • Be an assertion, to make explicit that this is a precondition of the function and detect precondition violations
  • Include a comment with a rock-solid explanation for why (1) this situation is allowed/expected to arise in ordinary execution, and (2) this particular handling of the situation is 100% correct and the only logical thing to do

The above rules should apply to every single early-out from a function/loop/etc. that appears in any PR for the Slang codebase. This is not a one-off problem; agents love to write garbage like this.

Comment thread source/slang/slang-target.cpp Outdated
Comment on lines +265 to +267
// For GLSL-based targets, SPIRV version and extension atoms are intentionally
// converted to their GLSL-SPIRV equivalents by getTargetCaps(), so they are
// not an error here.

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.

This seems like a bug, unrelated to the current PR.

If there is a rationale for why the SPIRV version atoms and the GLSL-SPIRV version atoms are distinct, is that documented somewhere? And if it is an intentional choice with a documented rationale... is it actually okay to then treat both cases as equivalent here?

@nv-slang-bot If the answer is "there is no clear rationale" then open an issue to note that the duplication there is probably a mistake, and we should consolidate on a single set of capability atoms for SPIRV versions, with the GLSL ones just being turned into aliases over the SPIRV ones.

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.

There is a documented, intentional rationale, so per your conditional this doesn't warrant a "the duplication is a mistake" issue — but the maintainability smell you're sensing is real, and I'll spell out where the line is.

Why the two families are distinct (documented): they belong to different code-gen targets, not two spellings of the same thing.

  • def _spirv_1_0 : spirv;_spirv_1_6 — rooted in the spirv target = direct SPIR-V codegen (slang-capabilities.capdef:162-168).
  • def glsl_spirv_1_0 : glsl;glsl_spirv_1_6 — rooted in the glsl target = SPIR-V produced via GLSL → glslang (slang-capabilities.capdef:131-155).

The rationale is stated in the capdef itself at slang-capabilities.capdef:126-127:

// Capabilities that stand for target SPIR-V versions for the GLSL backend.
// These are not compilation targets. We will convert spirvtoglsl_spirv_ during compilation.

and per-atom ("Represents SPIR-V 1.5 through glslang" vs "Represents SPIR-V 1.5 version"), which surfaces in the generated reference docs/user-guide/a4-02-reference-capability-atoms.md. The distinction was introduced deliberately in #4241.

A CapabilitySet partitions its atoms by target family (m_targetSets keyed on CapabilityAtom::spirv / CapabilityAtom::glsl, slang-capability.cpp:1577-1579), so a given version atom lives in exactly one family. spirv_1_5 means "SPIR-V 1.5 on the direct path"; glsl_spirv_1_5 means "SPIR-V 1.5 on the glslang path" — genuinely different pipelines with different feature availability. Concretely, _GL_EXT_ray_query : _GLSL_460 + glsl_spirv_1_4 (slang-capabilities.capdef:1007) requires the GLSL-path 1.4 atom specifically, with the inline note "spirv_1_4 is required due to glslang bug which enables SPV_KHR_ray_tracing regardless of context" — that requirement is only meaningful on the glslang path. And the sm_6_x version aliases carry both families side-by-side (sm_6_0_version = _sm_6_0 | _GLSL_450 | spirv_1_3 | …, slang-capabilities.capdef:1792-1799), i.e. they coexist rather than substitute for each other.

Is it OK to treat them as equivalent here? Yes — and note checkCapabilities isn't asserting they're equal, it's exempting SPIRV version/extension atoms from the incompatibility error on GLSL-based targets. That exemption is correct because getTargetCaps() already converts them: addSpirvVersionFromOtherAsGlslSpirvVersion (slang-capability.cpp:1575-1611, called at slang-target.cpp:149 and :229) moves a requested spirv_1_x into the glsl family as glsl_spirv_1_x. So -capability spirv_1_5 -target glsl is genuinely not incompatible — the conversion happens — and flagging it would be a false positive. The exemption is the right call.

On the consolidation proposal specifically: turning the glsl_spirv_* atoms into aliases over the spirv_* ones as literally described would re-root them into the spirv target family, which (a) breaks GLSL-path gates like the _GL_EXT_ray_query one above, and (b) leaves addSpirvVersionFromOtherAsGlslSpirvVersion with no distinct target to convert into. So that exact remedy isn't viable.

Where I agree there's a smell: two parallel version ladders kept in manual lockstep, plus the conversion done by raw enum-offset arithmetic (maybeConvertSpirvVersionToGlslSpirvVersion, slang-capability.cpp:1565-1573) that silently assumes _spirv_1_x and glsl_spirv_1_x stay contiguous and aligned in the generated enum. That's fragile, but it's a maintainability/robustness concern, not evidence of accidental duplication. If you'd like, I can file a narrower tracking issue for making that mapping declarative (or asserting the ordering invariant) rather than positional — just say the word and I'll open it and link it here.

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

Comment thread source/slang/slang-target.cpp Outdated
Comment on lines +273 to +277
// Capabilities inherited from the session-level option set (via inheritFrom) are
// intentionally broad — a multi-backend application may set e.g. hlsl_nvapi at
// the session level to enable it for D3D12 targets while also compiling for
// Vulkan/SPIRV in the same session. Only flag capabilities that were explicitly
// requested for this specific target, not ones that arrived via session inheritance.

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.

This seems like a dangerous policy decision to be introducing by fiat here. Is the stated policy documented somewhere, or is it just a "do what I mean" policy choice being made locally to try and not break some existing use cases?

AFAICT the whole point of this PR is to be more strict about how we diagnose situations where capabilities are being requested that are logically inconsistent with one another or the target, so it seems to actively go against that goal to sweep some cases of inconsistency under the rug and not others.

Comment thread source/slang/slang-target.cpp Outdated
// Vulkan/SPIRV in the same session. Only flag capabilities that were explicitly
// requested for this specific target, not ones that arrived via session inheritance.
auto sessionCapArray = getLinkage()->m_optionSet.getArray(CompilerOptionName::Capability);
auto isSessionLevelCap = [&](const CompilerOptionValue& atomVal) -> bool

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.

Actual question: is the codebase-wide policy in favor of using locally-defined lambdas for helper routines rather than defining them as file-level static functions? I have a personal preference for file-level statics because they make the contract of an operation explicit, force careful thought about naming, and encourage the author to write a thoughtful documentation comment explaining what they are doing.

If the codebase policy is in favor of this lambda-based idiom, I'll take it up with the policy separately. If there is no such policy, then I'd push for an actual subroutine instead of a lambda, where possible.

Comment thread source/slang/slang-target.cpp Outdated
if (isGLSLTarget && toAdd.getCapabilityTargetSets().containsKey(CapabilityAtom::spirv))
continue;

if (!cookedCaps.isIncompatibleWith(toAdd))

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.

Suggest adding an isCompatibleWith helper to the capability-set type used here, rather than use a double-negative in a condition like this.

…ce model

Address tangent-vector's architectural review of the E36121 check: rather than
TargetRequest::checkCapabilities() re-decoding raw -capability option values and
reconstructing "did this come from the session or the target" by comparing option
sets after the fact, capability requirements are now tagged with where they came
from at the point they are produced, and validated through one shared primitive.

- slang-capability.h/.cpp: add CapabilitySource (SessionOption / TargetOption /
  EntryPointRequirement) and SourcedCapabilityRequirement, plus
  findIncompatibleCapabilityRequirements(), the single place that checks a list of
  sourced capability requirements against a target's capabilities.
- slang-compiler-options.h/.cpp: CompilerOptionValue carries a capabilitySource
  field for Capability-kind entries, set once at the producer
  (CompilerOptionSet::load()/addCapabilityAtom()) and preserved automatically
  through inheritFrom/overrideWith merges instead of being reconstructed later.
- slang-target.cpp: checkCapabilities() now reads each entry's capabilitySource
  directly and calls the shared helper; the hand-rolled isSessionLevelCap lambda
  is gone. Replaced the null-sink early return with a release assert (every call
  site has a live sink) and gave isGLSLBasedTarget() a what-then-why doc comment.
- slang-check-shader.cpp: validateEntryPoint()'s target-vs-entry-point
  compatibility test now goes through the same findIncompatibleCapabilityRequirements()
  helper checkCapabilities() uses, so a target's own capabilities and an entry
  point's required capabilities are checked by one shared rule.

The GLSL-target SPIRV exemption in checkCapabilities() is left as-is behaviorally
(still broader than what getTargetCaps() actually converts) with a TODO pointing
at a follow-up issue, per review discussion: it's a pre-existing, documented
atom-family distinction rather than something to fix as a side effect here.

Fixes shader-slang#4422
github-actions[bot]

This comment was marked as outdated.

…fix stale comment

- checkCapabilities() now latches on a per-target bool guarded by m_mutex, so a
  persistent session that loads many modules against the same target doesn't
  re-derive and re-diagnose the same target-capability incompatibility on every
  module load (checkEntryPoints() runs once per FrontEndCompileRequest, i.e.
  once per module load, not once per target's lifetime).
- decodeCapabilityOption() now optionally writes the capability's display name
  through an out-parameter, so checkCapabilities() no longer re-opens a second
  switch (atomVal.kind) that duplicated (with divergent guards) the one already
  inside the shared decode helper. The redundant SLANG_CAPABILITY_UNKNOWN /
  CapabilityName::Invalid pre-checks are gone too: decodeCapabilityOption()
  already produces an empty CapabilitySet for those cases, which the existing
  toAdd.isEmpty() check already skips.
- isGLSLBasedTarget()'s doc comment no longer says "keep in sync with
  getTargetCaps()" -- after this PR's earlier commit, getTargetCaps() reads
  its own isGLSLTarget local from isGLSLBasedTarget() rather than duplicating
  the classification, so isGLSLBasedTarget() is the single source of truth,
  not one of two things to keep in sync.
- Filled in the TODO's placeholder issue link (issues/NNNN) with the real
  tracking issue (shader-slang#12703) for the over-broad GLSL/SPIRV-extension exemption.
The test row it labels was changed to target cpp (dropping the stale
-capability image_loadstore that now correctly triggers E36121), but the
filecheck label was left as CHECK_METAL, which is misleading -- there is no
Metal target in this file. Renamed to CHECK_CPP to match what the row
actually tests.
…y decode path

Every existing test drives -capability from the CLI, which always produces an
Int-kind CompilerOptionValue (slang-options.cpp's addCapabilityAtom ->
CompilerOptionValue::fromEnum), and the existing C++ unit tests both use the
Int-kind form too. decodeCapabilityOption()'s String-kind branch -- reachable
only via the C++ API (CompilerOptionValueKind::String with a capability name
string) -- had no regression coverage. Added a unit test that requests an
incompatible capability by name as a String-kind TargetDesc.compilerOptionEntries
entry and confirms E36121 still fires.
github-actions[bot]

This comment was marked as outdated.

zangold-nv added a commit to zangold-nv/slang that referenced this pull request Aug 24, 2026
…plication

Extract shared resolveHumaneLoc/makeDiagnosticSpan helpers in
richDiagnosticThunk to eliminate the duplicated primary/secondary
span field-mapping logic, and add rock-solid rationale comments to
the early-out guards in richDiagnosticThunk, installDiagnosticCallback,
and DiagnosticSink::diagnoseRichImpl, per tangent-vector's recurring
review feedback on PRs shader-slang#11225 and shader-slang#12421 about unexplained early-outs
and duplicated logic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@nv-slang-bot

nv-slang-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Follow-up fix for the TODO(#12703) at the GLSL SPIRV exemption

This resolves the TODO this PR left in TargetRequest::checkCapabilities() — a SPIRV extension capability requested on a GLSL-based target was silently dropped by getTargetCaps() and silently exempted from E36121, so it produced no error and no effect.

Fix: narrow the exemption to a genuine SPIRV version request via a new file-local isSpirvVersionRequest(). A pure extension request (e.g. -capability SPV_KHR_ray_tracing -target glsl) now correctly reports E36121; a version request (spirv_1_5) and cross-target aliases (sm_6_0) stay clean.

Key subtlety (why the naive "exempt only version atoms" test is wrong): spirv_1_5 and SPV_KHR_ray_tracing are indistinguishable in the flattened SPIRV atom closure — a version alias bundles extension atoms, and a bare extension (def SPV_KHR_ray_tracing : _spirv_1_4) transitively implies a version floor. The classification therefore keys on the non-implied leaf atoms (newSetWithoutImpliedAtoms()): a version alias keeps its version atom as a leaf; an extension implies its version floor away. Reuses the existing isSpirvVersionAtom predicate — no new equivalence helper. (Confirmed against this PR's own incompatible-capability-for-target.slang CHECK_GLSL_OK case, which requires -target glsl -capability spirv_1_5 to stay clean — the naive test would break it.)

Includes a regression test (tests/language-feature/capability/spirv-ext-capability-on-glsl-target.slang): extension → E36121 (plain -target glsl and -emit-spirv-via-glsl); spirv_1_5/sm_6_0 controls clean; version+extension as separate options classified independently (with a -NOT guard against a spurious spirv_1_5 diagnostic). Verified by direct slangc on each command line (the FileCheck sub-tests run under CI).

Delivered as a branch rather than a PR because I don't have write access to open a PR into this fork's gh-4422 branch. Two ways to pick it up:

A. Fetch the branch (based on this PR's head fd5a1b882d):

git fetch https://github.com/shader-slang/slang.git fix/issue-12703
git cherry-pick FETCH_HEAD   # or: git merge FETCH_HEAD

B. Apply the patch below — save the expanded block as fix-12703.patch, then:

git apply fix-12703.patch
fix-12703.patch
diff --git a/source/slang/slang-target.cpp b/source/slang/slang-target.cpp
index 4f2e4d0fd..bf8f4a5f4 100644
--- a/source/slang/slang-target.cpp
+++ b/source/slang/slang-target.cpp
@@ -271,6 +271,30 @@ CapabilitySet TargetRequest::getTargetCaps()
     return cookedCapabilities;
 }
 
+// Distinguishes a SPIRV *version* request (e.g. `spirv_1_5`) from a SPIRV *extension*-only request
+// (e.g. `SPV_KHR_ray_tracing`) -- the two are indistinguishable in the flattened atom closure (a
+// version alias bundles extension atoms; an extension implies a version floor), so the test is over
+// the non-implied (leaf) atoms: a version alias keeps its version atom as a leaf while an extension
+// implies its version floor away, hence a version was requested iff a version leaf survives.
+static bool isSpirvVersionRequest(const CapabilitySet& caps)
+{
+    auto* spirvTargetSet = caps.getCapabilityTargetSets().tryGetValue(CapabilityAtom::spirv);
+    if (!spirvTargetSet)
+        return false;
+
+    for (auto& stageSet : spirvTargetSet->getShaderStageSets())
+    {
+        if (!stageSet.second.atomSet)
+            continue;
+        for (auto atom : stageSet.second.atomSet->newSetWithoutImpliedAtoms())
+        {
+            if (isSpirvVersionAtom(asAtom(atom)))
+                return true;
+        }
+    }
+    return false;
+}
+
 void TargetRequest::checkCapabilities(DiagnosticSink* sink)
 {
     // Every call site (currently only `FrontEndCompileRequest::checkEntryPoints()`) has a
@@ -317,21 +341,9 @@ void TargetRequest::checkCapabilities(DiagnosticSink* sink)
         if (toAdd.isEmpty())
             continue;
 
-        // For GLSL-SPIRV pipeline targets, SPIRV version caps are intentionally converted
-        // to their glsl_spirv_* equivalents by getTargetCaps() (see isGLSLBasedTarget()'s
-        // comment), so they are not an error here.
-        //
-        // TODO(https://github.com/shader-slang/slang/issues/12703): this exemption is
-        // broader than the conversion it is meant to mirror. getTargetCaps() only
-        // converts SPIRV *version* atoms, but this test also exempts SPIRV *extension*
-        // atoms via the same "belongs to the spirv target family" check, so a SPIRV
-        // extension capability requested on a GLSL-based target is silently dropped by
-        // both getTargetCaps() and this check instead of being flagged. That mismatch
-        // predates this function (getTargetCaps() has always silently dropped
-        // incompatible explicit capability requests) and needs its own fix to
-        // getTargetCaps()'s conversion/exemption logic; narrowing just this test would
-        // just move where the silent drop happens.
-        if (isGLSLTarget && toAdd.getCapabilityTargetSets().containsKey(CapabilityAtom::spirv))
+        // On GLSL-based targets getTargetCaps() converts a requested SPIRV version to its
+        // glsl_spirv_* equivalent but has no realization for a SPIRV extension.
+        if (isGLSLTarget && isSpirvVersionRequest(toAdd))
             continue;
 
         requirements.add({toAdd, atomVal.capabilitySource, requestedCapName});
diff --git a/tests/language-feature/capability/spirv-ext-capability-on-glsl-target.slang b/tests/language-feature/capability/spirv-ext-capability-on-glsl-target.slang
new file mode 100644
index 000000000..40fe18795
--- /dev/null
+++ b/tests/language-feature/capability/spirv-ext-capability-on-glsl-target.slang
@@ -0,0 +1,31 @@
+//TEST:SIMPLE(filecheck=CHECK_EXT_ERR): -target glsl -entry main -stage compute -capability SPV_KHR_ray_tracing
+// CHECK_EXT_ERR: error[E36121]
+// CHECK_EXT_ERR: SPV_KHR_ray_tracing
+
+// The GLSL-SPIRV pipeline (-emit-spirv-via-glsl) hits the same isGLSLBasedTarget() exemption, so
+// the extension must be diagnosed there too.
+//TEST:SIMPLE(filecheck=CHECK_VIA_GLSL_EXT_ERR): -target spirv -emit-spirv-via-glsl -entry main -stage compute -capability SPV_KHR_ray_tracing
+// CHECK_VIA_GLSL_EXT_ERR: error[E36121]
+// CHECK_VIA_GLSL_EXT_ERR: SPV_KHR_ray_tracing
+
+// spirv_1_5's expansion also carries SPIRV extension atoms, so this pins that the exemption keys on
+// the requested version, not on the flattened closure.
+//TEST:SIMPLE(filecheck=CHECK_VER_OK): -target glsl -entry main -stage compute -capability spirv_1_5
+// CHECK_VER_OK: result code = 0
+// CHECK_VER_OK-NOT: E36121
+
+// Each -capability option is classified independently: the extension is diagnosed while the version
+// option stays exempt (the -NOT guards against a spurious E36121 on spirv_1_5).
+//TEST:SIMPLE(filecheck=CHECK_MIXED): -target glsl -entry main -stage compute -capability spirv_1_5 -capability SPV_KHR_ray_tracing
+// CHECK_MIXED: error[E36121]
+// CHECK_MIXED: SPV_KHR_ray_tracing
+// CHECK_MIXED-NOT: spirv_1_5
+
+// sm_6_0 is compatible with a GLSL target via its _GLSL_450 disjunct (capdef sm_6_0_version), so it
+// must not be flagged.
+//TEST:SIMPLE(filecheck=CHECK_CROSS_OK): -target glsl -entry main -stage compute -capability sm_6_0
+// CHECK_CROSS_OK: result code = 0
+// CHECK_CROSS_OK-NOT: E36121
+
+[numthreads(1, 1, 1)]
+void main() {}

Happy to adjust framing or fold it in whatever way is easiest for you.

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

… is requested at both session and target scope

CompilerOptionSet::add()'s duplicate-merge branch (replaceDuplicate=true,
used by overrideWith()) only copied intValue2/stringValue2 into the existing
entry, not capabilitySource. Linkage::addTarget() builds a target's option
set as inheritFrom(m_optionSet) (copies session-level entries, tagged
SessionOption) followed by overrideWith(targetOptions) (merges in
target-level entries, tagged TargetOption). When the same capability atom is
requested at both scopes, the second (target-level) entry is treated as a
duplicate of the first (session-level) one rather than a new entry, so the
existing SessionOption tag silently won -- a genuinely binding target-level
request was tolerated by checkCapabilities() as if it were a best-effort
session broadcast, with no E36121.

Copy capabilitySource in the same branch so a later overrideWith() entry's
source correctly replaces the inherited one, matching overrideWith()'s
documented "copy settings from other, and replace the current setting"
semantics. Added a unit test (sameCapabilityAtBothScopesStillFlagged) that
requests the same capability at both scopes and asserts E36121 still fires.
… E36121 on later module loads

The latch added in 9ffd7fd ("cache checkCapabilities()... so a persistent
session doesn't re-diagnose the same target-capability incompatibility on
every module load") was wrong: m_capabilitiesChecked lives on the
TargetRequest, which is shared across the Linkage's whole session lifetime,
not per-compile. So only the *first* loadModule() call against a
misconfigured target got E36121; every later load against that same target
silently returned no diagnostic in its own (independently inspected)
diagnostic blob, even though that load's compile is just as invalid.

The "duplicate diagnostics across loads" observation this was fixing was
never actually a bug: every other target-config diagnostic in this file
(e.g. the profile/stage-mismatch check in validateEntryPoint()) runs
uncached on every FrontEndCompileRequest, and that is correct -- a fresh
DiagnosticSink is created per module load, and each one must independently
reflect whether that load's compile is valid. Restoring that behavior here.
…not being flagged

sm_6_0 (and aliases like it) intentionally spans HLSL, SPIRV, CUDA, Metal,
CPP and LLVM -- it must not be flagged as incompatible on any target it
spans. This was previously documented only as a prose comment; a regression
here would have gone undetected. Added SIMPLE tests requesting sm_6_0 on
both an HLSL and a SPIRV target and asserting no E36121 on either.
…int's use of findIncompatibleCapabilityRequirements

Note that the single-element SourcedCapabilityRequirement wrapping at this
call site only exists to route through the same shared compatibility
primitive checkCapabilities() uses; source and label aren't read here, only
the returned count is.

@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: 🟡 Minor issues — 0 bugs, 3 gaps (1 test, 2 clarity/maintainability)

This PR adds error E36121 for -capability requests incompatible with the code-gen target, promotes E36112 (profile/stage mismatch) from warning to error, and threads a CapabilitySource provenance (SessionOption/TargetOption/EntryPointRequirement) through CompilerOptionValue so session-scope requests are tolerated while target-scope ones are diagnosed. Memory-safety and IR/check-correctness passes found no defects; the session-vs-target merge logic is correct and unit-tested. Findings are limited to one vacuous test case and two clarity/maintainability items.

Changes Overview

Capability provenance model (slang-capability.h/.cpp, slang-compiler-options.h/.cpp)

  • Adds CapabilitySource enum + SourcedCapabilityRequirement, tags CompilerOptionName::Capability entries with their origin at load()/addCapabilityAtom(), and carries the tag through inheritFrom/overrideWith merges. New shared helper findIncompatibleCapabilityRequirements.

Target-capability validation (slang-target.cpp/.h, slang-check-shader.cpp, slang-diagnostics.lua)

  • New TargetRequest::checkCapabilities(sink) (called once per target in checkEntryPoints()) diagnoses target-scope incompatible caps as E36121, exempting GLSL-based targets for SPIRV caps (auto-converted). Extracts isGLSLBasedTarget()/decodeCapabilityOption() as single-source-of-truth helpers. validateEntryPoint reroutes its incompatibility check through the shared helper. E36112 promoted warning→error.

Provenance plumbing (slang-global-session.cpp, slang-session.cpp, slang-linkable.cpp)

  • Session-desc load → SessionOption; target-desc load and linkWithOptionsTargetOption.

Tests (incompatible-capability-for-target.slang new, unit-test-session-capability-not-flagged.cpp new, conflicting-profile-stage-for-entry-point.slang, specializeTargetSwitch.slang)

  • Cross-target E36121 matrix, -ignore-capabilities suppression, GLSL auto-conversion, cross-target aliases; 4 unit tests for session-vs-target scope handling; E36112 expectation updated warning→error.
Findings (3 total)
Severity Location Finding
🟡 Gap tests/language-feature/capability/incompatible-capability-for-target.slang:16 CHECK_VIA_GLSL_OK has only a -NOT directive — the sole test of the SPIRV-via-GLSL branch passes vacuously
🟡 Gap source/slang/slang-compiler-options.h:181 capabilitySource merged unconditionally for all option kinds via a hand-maintained field list
🟡 Gap source/slang/slang-capability.h:539 CapabilitySource enum mixes *Option/*Requirement naming conventions

reviewed: aa4fa4d · diff sha256 617820238a6c

// cap is auto-converted to a glsl_spirv_* equivalent by getTargetCaps(), so no error.
// This exercises the isGLSLBasedTarget() + SPIRV-cap exemption branch.
//TEST:SIMPLE(filecheck=CHECK_VIA_GLSL_OK): -target spirv -emit-spirv-via-glsl -entry main -stage compute -capability spirv_1_5
// CHECK_VIA_GLSL_OK-NOT: E36121

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: CHECK_VIA_GLSL_OK is a vacuous (negative-only) check

This case is the only test that exercises the new isGLSLBasedTarget() SPIRV-via-GLSL branch (case SPIRV/SPIRVAssembly: return !optionSet.shouldEmitSPIRVDirectly();) — the comment above it even says "This exercises the isGLSLBasedTarget() + SPIRV-cap exemption branch." But its only directive is -NOT: E36121 with no positive assertion. If the -emit-spirv-via-glsl pipeline fails or emits nothing for any unrelated reason, the test still passes because E36121 is merely absent, so the branch it is meant to guard would not actually be verified.

Every other OK case has a positive check (CHECK_OK/CHECK_SPIRV_DIRECT_OK assert OpEntryPoint; CHECK_IGNORE/CHECK_GLSL_OK assert result code = 0); only this one does not.

Suggestion: Add a positive assertion alongside the -NOT, e.g.

// CHECK_VIA_GLSL_OK: result code = 0
// CHECK_VIA_GLSL_OK-NOT: E36121

{
(*v)[index].intValue2 = element.intValue2;
(*v)[index].stringValue2 = element.stringValue2;
(*v)[index].capabilitySource = element.capabilitySource;

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: capabilitySource is merged unconditionally for every option kind, via a hand-maintained field list

This replaceDuplicate branch merges an enumerated subset of CompilerOptionValue's fields into an existing duplicate (intValue2, stringValue2, and now capabilitySource), rather than the whole value — the dedup key (intValue/stringValue) is deliberately excluded. Two concerns:

  1. Provenance is copied for all option names, not just Capability. capabilitySource is documented as "Meaningless for every other option name," so copying it for e.g. a duplicate Profile entry is harmless today, but it reads as unintended since the field's meaning is capability-specific.
  2. Standing fragility. Any future semantically-significant field added to CompilerOptionValue must be remembered here or it is silently dropped on dedup. Adding capabilitySource to this list is the correct fix (verified by sameCapabilityAtBothScopesStillFlagged), but the omission of other fields is not obviously deliberate to a future maintainer.

Suggestion: Either guard the assignment so intent is explicit —

if (name == CompilerOptionName::Capability)
    (*v)[index].capabilitySource = element.capabilitySource;

— or add a one-line comment stating that on a duplicate only these payload fields are merged (the match key intentionally is not), so the field-by-field copy reads as intentional.


/// Identifies which part of a compile produced a capability requirement that is being
/// checked against a target's capabilities.
enum class CapabilitySource

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: CapabilitySource enum mixes two naming conventions

SessionOption/TargetOption name the mechanism (a compiler option), while EntryPointRequirement names the concept (a requirement). Since the enum represents "where a capability requirement came from," a reader has to reconcile whether these are three kinds of option-source or three kinds of requirement-source. Aligning the case names on one axis (all as request scopes, or all as requirement sources) would let the exhaustive set read as one decomposition. Cosmetic — the per-case doc comments are otherwise thorough.

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

Labels

pr: breaking change PRs with breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Capabilities System: Check if set capabilities are incompatible with the current target/stage, error if not compatible

3 participants