Skip to content

Commit 0d8e757

Browse files
Add IGlobalSession::getDownstreamCompilerVersion (#11552) (#11556)
## Motivation There is no public way to ask Slang which version of a downstream pass-through compiler it will *actually load and use*. This matters for NVRTC: an application (Falcor) wants to guard a CUDA diagnostic path that is pathological on older NVRTC and fine on newer, and the guard must key off the NVRTC library *Slang* selected — Slang's discovery order (an explicit `setDownstreamCompilerPath`, then the instance directory, then `CUDA_PATH`, then `PATH`, picking the newest match) can differ from a version the application discovers on its own by calling `nvrtcVersion()`. Concrete consumer: ```cpp int major = 0, minor = 0; if (SLANG_SUCCEEDED(globalSession->getDownstreamCompilerVersion(SLANG_PASS_THROUGH_NVRTC, &major, &minor))) useOldNvrtcWorkaround = (major < 12) || (major == 12 && minor < 7); ``` The version is already captured internally: `NVRTCDownstreamCompiler::init()` calls the loaded library's `nvrtcVersion(&major, &minor)` and stores it in `m_desc.version` (`source/compiler-core/slang-nvrtc-compiler.cpp:196-197`). This change just exposes that captured value through a public, ABI-additive method. ## Proposed solution Append a single method to `IGlobalSession`: ```cpp virtual SLANG_NO_THROW SlangResult SLANG_MCALL getDownstreamCompilerVersion(SlangPassThrough passThrough, int* outMajor, int* outMinor) = 0; ``` The implementation routes through `Session::getOrLoadDownstreamCompiler(passThrough, nullptr)` — the same memoized lazy-discovery funnel that compilation uses — and reads the numeric version from `getDesc().version`. Routing through that funnel is the load-bearing correctness property: it shares the memoized compiler cache with `SLANG_PTX` compilation and honors `setDownstreamCompilerPath` plus the standard search order, so the reported version is guaranteed to be the library Slang will actually compile with. **Why numeric (`int* outMajor, int* outMinor`) and not a version string?** A string-blob shape (`getVersionString`) is unusable for the very compiler this feature targets: `NVRTCDownstreamCompiler` does **not** override `getVersionString` (only DXC, glslang, and Tint do), so it inherits the `DownstreamCompilerBase` default that returns `SLANG_FAIL` and a null blob (`source/compiler-core/slang-downstream-compiler.h:401-406`). The version is reliably reachable only through `getDesc().version`. A numeric pair is also directly usable for the version-comparison guard that is the actual use case — no string-format contract to freeze and no client-side parsing. A public descriptor struct was rejected as an unnecessary permanent ABI commitment. > **Maintainer decision point (public ABI shape).** This is a permanent public-API surface. We > recommend the numeric `int* outMajor, int* outMinor` shape above for the reasons given. If you > prefer a different shape — e.g. a version *string* blob (would additionally require adding a > `getVersionString` override to `NVRTCDownstreamCompiler`), or a richer descriptor — we are happy to > adjust. Flagging because the choice is hard to change once released. ## Change summary | File | Change | |------|--------| | `include/slang.h` | Append the new pure-virtual to `IGlobalSession`, immediately before the closing `};` (after `saveBuiltinModule`) — append-only, ABI-safe. | | `source/slang/slang-global-session.h` | Declare the `Session` override. | | `source/slang/slang-global-session.cpp` | Implement: boundary-validate the pass-through, load via `getOrLoadDownstreamCompiler`, read `getDesc().version`. | | `source/slang-record-replay/proxy/proxy-global-session.h` | `GlobalSessionProxy` override (record/replay forwarding, records the version out-params). | | `source/slang-record-replay/replay-handlers.cpp` | Register the new method's replay handler so recorded streams containing the call dispatch on replay. | | `tools/slang-unit-test/unit-test-vtable-stability.cpp` | Add the new method to the `IGlobalSession` vtable-layout probe (slot 32) and assert it. | | `tools/slang-unit-test/unit-test-downstream-compiler-version.cpp` (new) | GPU-free unit test for the new API. | ## Concepts and vocabulary - **`getOrLoadDownstreamCompiler`** — the single, mutex-guarded, memoized funnel that finds and loads a downstream compiler for a pass-through, applying the override/instance-dir/`CUDA_PATH`/`PATH` search order and caching the result. All compilation goes through it; routing the query through it is what guarantees the reported version matches the library used for codegen. - **`DownstreamCompilerDesc::version`** — a `SemanticVersion` (`m_major`/`m_minor`/`m_patch`) on the desc returned by `IDownstreamCompiler::getDesc()`. For NVRTC it is populated from `nvrtcVersion` at load time; for compilers that report no version it is `(0,0)`. - **vtable-stability probe** — `tools/slang-unit-test/unit-test-vtable-stability.cpp` pins the COM vtable slot index of every `IGlobalSession` method; appending a method adds the next slot, which the test now covers, guarding against accidental mid-vtable insertion. - **record/replay proxy** — `GlobalSessionProxy` wraps a real `IGlobalSession` to record API calls for later replay; every interface method needs a forwarding override here. ## Process report - **`include/slang.h` (append the virtual).** The method is appended at the very end of `IGlobalSession`, after the previously-last method `saveBuiltinModule`, with an explicit doc comment. This is the only ABI-safe placement: inserting mid-interface would shift every subsequent vtable slot and break callers compiled against the old header. No enum or struct is added, so the change is `pr: non-breaking`. - **`source/slang/slang-global-session.h` (declaration).** Declares the override on the concrete `Session` class. This is required because `Session` implements `IGlobalSession`; an appended pure virtual makes the class abstract until overridden. The declaration order here is cosmetic — the vtable slot order is fixed solely by the interface declaration in `include/slang.h`. - **`source/slang/slang-global-session.cpp` (implementation).** Mirrors the existing `checkPassThroughSupport` → `getOrLoadDownstreamCompiler` precedent for load and error semantics (`SLANG_E_NOT_FOUND` when the compiler is not loadable). It then reads the numeric version directly from `compiler->getDesc().version` rather than from `getVersionString`. *Input-shape check:* the data read here — `getDesc().version` — is the correct, principled source. It is exactly the field `NVRTCDownstreamCompiler::init()` populates from the loaded library and the same field NVRTC uses to gate its own behavior, so there is no upstream producer to fix; the string accessor is simply not implemented for NVRTC, which is why we read the desc. A public boundary check rejects out-of-range `SlangPassThrough` values up front (the loader indexes per-type arrays by the enum value, so an out-of-range value would otherwise index out of bounds); `SLANG_PASS_THROUGH_NONE` returns `SLANG_E_NOT_FOUND` because there is no compiler to report (this intentionally differs from `checkPassThroughSupport(NONE) == SLANG_OK`). `outMajor`/`outMinor` are optional (null tolerated). A loadable-but-versionless compiler yields `SLANG_OK` with `(0,0)`, consistent with "the loaded compiler's reported version." - **`source/slang-record-replay/proxy/proxy-global-session.h` (proxy override).** Required because `GlobalSessionProxy` implements `IGlobalSession`; without the override the class would not compile. It records the call and input, then — following the scalar-out-param pattern already used by `getTypeConformanceWitnessSequentialID` in the session proxy — uses `PREPARE_POINTER_OUTPUT` (which redirects a null arg to a stack temp, making the subsequent record null-safe) and `RECORD_OUTPUT` so the deterministic version values are captured for faithful replay. - **`source/slang-record-replay/replay-handlers.cpp` (replay registration).** Every `IGlobalSession` method that the proxy records has a matching `REPLAY_REGISTER` entry; without one, a recorded stream containing the new call would fail to dispatch on replay. The registration is added next to `saveBuiltinModule`, mirroring the interface's append order. - **`tools/slang-unit-test/unit-test-vtable-stability.cpp` (vtable guard).** The probe must implement every `IGlobalSession` method (pure virtuals), so the new method is added as slot 32 (after `saveBuiltinModule` = 31) and asserted. This is the regression guard proving the new method is the *last* slot — i.e. truly appended, not inserted. - **`tools/slang-unit-test/unit-test-downstream-compiler-version.cpp` (test).** A pure metadata query, so it runs without a GPU. It first asserts the invalid inputs — `SLANG_PASS_THROUGH_NONE` (with both real and null out-params, so null handling runs on every CI runner) and an out-of-range `SlangPassThrough(SLANG_PASS_THROUGH_COUNT_OF)` (which exercises the boundary guard) — all → `SLANG_E_NOT_FOUND`, before any availability probe so they cannot perturb the lazy cache. It covers the documented "loaded-but-versionless ⇒ `SLANG_OK` with `(0,0)`" clause via `SLANG_PASS_THROUGH_GLSLANG` (bundled, GPU-free, reports no numeric version) so a future regression conflating that with `SLANG_E_NOT_FOUND` would fail. Then, gated on `checkPassThroughSupport(SLANG_PASS_THROUGH_NVRTC)` exactly as the existing CUDA tests do, it asserts `SLANG_OK` with `major > 0` (and null out-params tolerated) when NVRTC is available, or `SLANG_E_NOT_FOUND` when it is not. CI runners with NVRTC exercise the populated path. ## CLI option `-<compiler>-version` (added per review request) @jkwak-work asked to surface this new API through a slangc command-line query option mirroring the existing `-<compiler>-path`, plus slang-test coverage. That is the final commit on this branch. **Behavior.** `-<compiler>-version` is a print-and-continue query flag with the same `-<compiler>-...` shape as `-<compiler>-path`: it consumes no value, prints `<compiler> version: <major>.<minor>` for the downstream compiler Slang would actually load for that pass-through, then lets compilation continue (exactly like `-version`). It honors `-<compiler>-path` and the standard search order because it routes through the same `getDownstreamCompilerVersion` funnel. When the toolchain cannot be located it prints `<compiler> version: not found` and still exits 0, so the `<compiler> version:` prefix is stable across machines. Examples: ``` slangc -dxc-version # -> "dxc version: 1.8" (or "dxc version: not found") slangc -glslang-version # -> "glslang version: 0.0" (loaded, reports no numeric version) slangc -nvrtc-version -dxc-version # prints both, in command-line order; consumes no value ``` **Why this layer.** The parser already owns the symmetric `-<compiler>-path` registration/handler; the new option reuses the same `NameValueUtil::getNames(... getCompilerInfos())` name expansion and the same `lastIndexOf('-')` name recovery, and the only new sink call is `diagnoseRaw(Note, ...)` (verbatim text, no error-count increment, exit stays 0). No new public surface beyond the appended `CompilerOptionName::CompilerVersion = 153` enumerator (CLI-only; never stored on an option set). | File | Change | |------|--------| | `include/slang.h` | Append `CompilerOptionName::CompilerVersion = 153` before `CountOf` (ABI-additive; CLI-only). | | `source/slang/slang-options.cpp` | Register `-<compiler>-version` (mirrors the `-<compiler>-path` block) and add the print-and-continue handler. | | `tests/downstream/downstream-compiler-version.slang` (new) | Comment-only `//TEST:SIMPLE(filecheck=...)` cases for dxc/fxc/glslang/nvrtc, the hyphenated `spirv-dis` name path, and a two-flag invocation — matching the stable `<compiler> version:` prefix so they are deterministic on any runner (GPU-free). | | `docs/command-line-slangc-reference.md` | Regenerated from `slangc -help-style markdown -h` to include the new option. | Closes #11552. --------- Co-authored-by: nv-slang-bot[bot] <274397474+nv-slang-bot[bot]@users.noreply.github.com> Co-authored-by: Harsh Aggarwal <haaggarwal@nvidia.com>
1 parent 168c59b commit 0d8e757

10 files changed

Lines changed: 290 additions & 1 deletion

docs/command-line-slangc-reference.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,16 @@ Specify path to a downstream [&lt;compiler&gt;](#compiler) executable or library
699699

700700

701701

702+
<a id="none-version"></a>
703+
### -&lt;compiler&gt;-version
704+
705+
**-&lt;[compiler](#compiler)&gt;-version**
706+
707+
Print the version of the downstream [&lt;compiler&gt;](#compiler) that Slang would load for that pass-through, then continue. Reports "not found" if the compiler cannot be located. Takes no value.
708+
709+
710+
711+
702712
<a id="default-downstream-compiler"></a>
703713
### -default-downstream-compiler
704714

include/slang.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,12 @@ typedef uint32_t SlangSizeT;
12041204
// watchdog timeouts heavy coverage can trigger) at the cost of exact
12051205
// counts. Off by default.
12061206

1207+
// CLI-only query option `-<compiler>-version`: prints the version of the downstream
1208+
// <compiler> Slang would actually load for that pass-through (via
1209+
// IGlobalSession::getDownstreamCompilerVersion). It takes no value and is never stored on
1210+
// an option set; it only drives the print-and-continue handler in the command-line parser.
1211+
CompilerVersion = 153,
1212+
12071213
CountOf,
12081214
};
12091215

@@ -4192,6 +4198,29 @@ struct IGlobalSession : public ISlangUnknown
41924198
BuiltinModuleName module,
41934199
SlangArchiveType archiveType,
41944200
ISlangBlob** outBlob) = 0;
4201+
4202+
/** Get the version of the downstream/pass-through compiler that Slang will actually load and
4203+
use for `passThrough`, applying the same lazy discovery and library search order used during
4204+
compilation. This lets a client key its behavior off the exact library Slang selected (for
4205+
example, the specific NVRTC that will compile CUDA), which can differ from a version the client
4206+
might discover on its own.
4207+
4208+
This is not a cheap accessor: the first call for a given `passThrough` performs discovery and
4209+
loads the downstream library into the process (then memoizes it for subsequent calls).
4210+
4211+
Only some downstream compilers report a numeric version (e.g. NVRTC, DXC, the C/C++ toolchains);
4212+
others (e.g. the glslang family and Tint) always report `(0,0)`. The version is read uniformly
4213+
from the loaded compiler's descriptor, so a versionless-but-loaded compiler still returns
4214+
SLANG_OK with major/minor 0 — which the result alone does not distinguish from a genuine 0.0.
4215+
@param passThrough The downstream compiler to query (e.g. SLANG_PASS_THROUGH_NVRTC).
4216+
@param outMajor Receives the major version number. May be null.
4217+
@param outMinor Receives the minor version number. May be null.
4218+
@return SLANG_OK if the compiler was located and loaded (see the versionless note above).
4219+
SLANG_E_NOT_FOUND if the compiler could not be located or loaded, and likewise for
4220+
SLANG_PASS_THROUGH_NONE or an out-of-range value — the result code alone does not distinguish an
4221+
invalid argument from a compiler that is simply not installed. */
4222+
virtual SLANG_NO_THROW SlangResult SLANG_MCALL
4223+
getDownstreamCompilerVersion(SlangPassThrough passThrough, int* outMajor, int* outMinor) = 0;
41954224
};
41964225

41974226
#define SLANG_UUID_IGlobalSession IGlobalSession::getTypeGuid()

source/slang-record-replay/proxy/proxy-global-session.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,24 @@ class GlobalSessionProxy : public ProxyBase<slang::IGlobalSession>
257257
RECORD_RETURN(result);
258258
}
259259

260+
virtual SLANG_NO_THROW SlangResult SLANG_MCALL getDownstreamCompilerVersion(
261+
SlangPassThrough passThrough,
262+
int* outMajor,
263+
int* outMinor) override
264+
{
265+
RECORD_CALL();
266+
RECORD_INPUT(passThrough);
267+
PREPARE_POINTER_OUTPUT(outMajor);
268+
PREPARE_POINTER_OUTPUT(outMinor);
269+
auto result = getActual<slang::IGlobalSession>()->getDownstreamCompilerVersion(
270+
passThrough,
271+
outMajor,
272+
outMinor);
273+
RECORD_OUTPUT(outMajor);
274+
RECORD_OUTPUT(outMinor);
275+
RECORD_RETURN(result);
276+
}
277+
260278
virtual SLANG_NO_THROW SlangResult SLANG_MCALL
261279
compileCoreModule(slang::CompileCoreModuleFlags flags) override
262280
{

source/slang-record-replay/replay-handlers.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ static void registerAllHandlers()
124124
REPLAY_REGISTER(GlobalSessionProxy, compileBuiltinModule);
125125
REPLAY_REGISTER(GlobalSessionProxy, loadBuiltinModule);
126126
REPLAY_REGISTER(GlobalSessionProxy, saveBuiltinModule);
127+
REPLAY_REGISTER(GlobalSessionProxy, getDownstreamCompilerVersion);
127128

128129
// =========================================================================
129130
// SessionProxy handlers

source/slang/slang-global-session.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,38 @@ SlangResult Session::checkPassThroughSupport(SlangPassThrough inPassThrough)
274274
return checkExternalCompilerSupport(this, PassThroughMode(inPassThrough));
275275
}
276276

277+
SlangResult Session::getDownstreamCompilerVersion(
278+
SlangPassThrough inPassThrough,
279+
int* outMajor,
280+
int* outMinor)
281+
{
282+
// Validate at the public boundary: only a real (non-None), in-range pass-through can name a
283+
// loadable compiler. getOrLoadDownstreamCompiler indexes per-type arrays by the enum value, so
284+
// reject out-of-range values here rather than indexing out of bounds.
285+
if (inPassThrough <= SLANG_PASS_THROUGH_NONE || inPassThrough >= SLANG_PASS_THROUGH_COUNT_OF)
286+
return SLANG_E_NOT_FOUND;
287+
288+
// Route through the same lazy-discovery funnel that compilation uses, so the reported version
289+
// is guaranteed to be the library that will actually be used for this pass-through (it shares
290+
// the memoized cache and honors setDownstreamCompilerPath + the standard search order).
291+
IDownstreamCompiler* compiler =
292+
getOrLoadDownstreamCompiler(PassThroughMode(inPassThrough), nullptr);
293+
if (!compiler)
294+
return SLANG_E_NOT_FOUND;
295+
296+
// Read the version from the loaded compiler's descriptor: it is the uniform numeric source
297+
// across pass-throughs, populated at load (e.g. NVRTC via nvrtcVersion in its init). For some
298+
// compilers it is the only place the version is exposed — NVRTC, for one, never implements
299+
// getVersionString — while others (glslang, Tint) leave it at (0,0). Hence the desc, not the
300+
// version string.
301+
const SemanticVersion& version = compiler->getDesc().version;
302+
if (outMajor)
303+
*outMajor = version.m_major;
304+
if (outMinor)
305+
*outMinor = version.m_minor;
306+
return SLANG_OK;
307+
}
308+
277309
void Session::writeCoreModuleDoc(String config)
278310
{
279311
ASTBuilder* astBuilder = getBuiltinLinkage()->getASTBuilder();

source/slang/slang-global-session.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ class Session : public RefObject, public slang::IGlobalSession
154154
checkCompileTargetSupport(SlangCompileTarget target) override;
155155
SLANG_NO_THROW SlangResult SLANG_MCALL
156156
checkPassThroughSupport(SlangPassThrough passThrough) override;
157+
SLANG_NO_THROW SlangResult SLANG_MCALL getDownstreamCompilerVersion(
158+
SlangPassThrough passThrough,
159+
int* outMajor,
160+
int* outMinor) override;
157161

158162
void writeCoreModuleDoc(String config);
159163
SLANG_NO_THROW SlangResult SLANG_MCALL

source/slang/slang-options.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,28 @@ void initCommandOptions(CommandOptions& options)
981981
"-<compiler>-path");
982982
}
983983

984+
{
985+
auto namesList = NameValueUtil::getNames(
986+
NameValueUtil::NameKind::First,
987+
TypeTextUtil::getCompilerInfos());
988+
StringBuilder names;
989+
for (auto name : namesList)
990+
{
991+
names << "-" << name << "-version,";
992+
}
993+
// remove last ,
994+
names.reduceLength(names.getLength() - 1);
995+
996+
options.add(
997+
names.getBuffer(),
998+
"-<compiler>-version",
999+
"Print the version of the downstream <compiler> that Slang would load for that "
1000+
"pass-through, then continue. Reports \"not found\" if the compiler cannot be "
1001+
"located. Takes no value.\n",
1002+
UserValue(OptionKind::CompilerVersion),
1003+
"-<compiler>-version");
1004+
}
1005+
9841006
const Option downstreamOpts[] = {
9851007
{OptionKind::DefaultDownstreamCompiler,
9861008
"-default-downstream-compiler",
@@ -3639,6 +3661,54 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv)
36393661
}
36403662
break;
36413663
}
3664+
case OptionKind::CompilerVersion:
3665+
{
3666+
// `-<compiler>-version` is a print-and-continue query option. It has the same
3667+
// "-<compiler>-..." shape as -<compiler>-path, but instead of consuming a value it
3668+
// prints the version of the downstream compiler Slang would actually load for that
3669+
// pass-through, then lets parsing continue (like -version). Recover <compiler> as
3670+
// the text between the leading '-' and the trailing "-version", exactly as the
3671+
// CompilerPath case recovers the name before "-path".
3672+
const Index index = argValue.lastIndexOf('-');
3673+
if (index >= 0)
3674+
{
3675+
UnownedStringSlice passThroughSlice =
3676+
argValue.getUnownedSlice().head(index).tail(1);
3677+
3678+
SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE;
3679+
if (SLANG_FAILED(TypeTextUtil::findPassThrough(passThroughSlice, passThrough)))
3680+
{
3681+
m_sink->diagnose(Diagnostics::UnknownDownstreamCompiler{
3682+
.compiler = passThroughSlice,
3683+
.location = arg.loc});
3684+
return SLANG_FAIL;
3685+
}
3686+
3687+
// getDownstreamCompilerVersion shares the same lazy-discovery funnel used
3688+
// during compilation, so the reported version is the library that would
3689+
// actually be used for this pass-through (it honors -<compiler>-path and the
3690+
// standard search order). It returns SLANG_OK once the compiler is located and
3691+
// loaded -- major/minor are then valid, and a loaded-but-versionless compiler
3692+
// such as glslang reports 0.0 -- and SLANG_E_NOT_FOUND when it cannot be
3693+
// loaded (e.g. the toolchain is not installed).
3694+
int major = 0;
3695+
int minor = 0;
3696+
StringBuilder versionStr;
3697+
versionStr << passThroughSlice << " version: ";
3698+
if (SLANG_SUCCEEDED(
3699+
m_session->getDownstreamCompilerVersion(passThrough, &major, &minor)))
3700+
{
3701+
versionStr << major << "." << minor;
3702+
}
3703+
else
3704+
{
3705+
versionStr << "not found";
3706+
}
3707+
versionStr << "\n";
3708+
m_sink->diagnoseRaw(Severity::Note, versionStr.getUnownedSlice());
3709+
}
3710+
break;
3711+
}
36423712
case OptionKind::InputFilesRemain:
36433713
{
36443714
// The `--` option causes us to stop trying to parse options,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// downstream-compiler-version.slang
2+
//
3+
// Exercise the `-<compiler>-version` query option (issue #11552 / PR #11556). Each invocation
4+
// prints the version of the downstream compiler that Slang would actually load for that
5+
// pass-through (via IGlobalSession::getDownstreamCompilerVersion), then lets compilation of this
6+
// comment-only file proceed and exit cleanly. The printed line has a stable "<compiler> version:"
7+
// prefix whether or not the toolchain is installed (it prints "not found" when the compiler
8+
// cannot be located), so these checks are deterministic across machines and need no GPU.
9+
10+
//TEST:SIMPLE(filecheck=DXC): -dxc-version
11+
//TEST:SIMPLE(filecheck=FXC): -fxc-version
12+
//TEST:SIMPLE(filecheck=GLSLANG): -glslang-version
13+
//TEST:SIMPLE(filecheck=NVRTC): -nvrtc-version
14+
// spirv-dis exercises the hyphenated-name path: `-spirv-dis-version` must recover "spirv-dis".
15+
//TEST:SIMPLE(filecheck=SPIRVDIS): -spirv-dis-version
16+
// Two query flags in one invocation: proves the option consumes no value (it must not swallow the
17+
// following `-glslang-version`), and that versions print in command-line order.
18+
//TEST:SIMPLE(filecheck=MULTI): -dxc-version -glslang-version
19+
20+
// DXC: dxc version:
21+
// FXC: fxc version:
22+
// GLSLANG: glslang version:
23+
// NVRTC: nvrtc version:
24+
// SPIRVDIS: spirv-dis version:
25+
// MULTI: dxc version:
26+
// MULTI: glslang version:
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// unit-test-downstream-compiler-version.cpp
2+
3+
#include "slang-com-ptr.h"
4+
#include "slang.h"
5+
#include "unit-test/slang-unit-test.h"
6+
7+
using namespace Slang;
8+
9+
// Test the IGlobalSession::getDownstreamCompilerVersion API (issue #11552). The method reports the
10+
// version of the downstream compiler Slang will actually load for a given pass-through, using the
11+
// same lazy discovery that compilation uses. This is purely a metadata query, so it runs without a
12+
// GPU: when the downstream compiler is loadable we expect a real version; otherwise the method
13+
// returns SLANG_E_NOT_FOUND, exactly like checkPassThroughSupport.
14+
SLANG_UNIT_TEST(getDownstreamCompilerVersion)
15+
{
16+
slang::IGlobalSession* globalSession = unitTestContext->slangGlobalSession;
17+
18+
// SLANG_PASS_THROUGH_NONE is never a loadable compiler. Also verify null out-params are
19+
// tolerated on this always-reachable path (the NVRTC null-out check below only runs where
20+
// NVRTC is installed).
21+
{
22+
int major = -1;
23+
int minor = -1;
24+
SLANG_CHECK(
25+
globalSession->getDownstreamCompilerVersion(SLANG_PASS_THROUGH_NONE, &major, &minor) ==
26+
SLANG_E_NOT_FOUND);
27+
SLANG_CHECK(
28+
globalSession->getDownstreamCompilerVersion(
29+
SLANG_PASS_THROUGH_NONE,
30+
nullptr,
31+
nullptr) == SLANG_E_NOT_FOUND);
32+
}
33+
34+
// An out-of-range pass-through value must be rejected at the boundary, not used to index the
35+
// per-type compiler arrays.
36+
{
37+
int major = -1;
38+
int minor = -1;
39+
SLANG_CHECK(
40+
globalSession->getDownstreamCompilerVersion(
41+
SlangPassThrough(SLANG_PASS_THROUGH_COUNT_OF),
42+
&major,
43+
&minor) == SLANG_E_NOT_FOUND);
44+
}
45+
46+
// Gate the NVRTC path on availability the same way the CUDA codegen tests do, so this test
47+
// passes both on machines that have NVRTC and those that do not (no GPU is required merely to
48+
// load the NVRTC library and read its version).
49+
const bool nvrtcAvailable =
50+
SLANG_SUCCEEDED(globalSession->checkPassThroughSupport(SLANG_PASS_THROUGH_NVRTC));
51+
52+
int major = -1;
53+
int minor = -1;
54+
const SlangResult result =
55+
globalSession->getDownstreamCompilerVersion(SLANG_PASS_THROUGH_NVRTC, &major, &minor);
56+
57+
if (nvrtcAvailable)
58+
{
59+
// The loaded NVRTC always reports a real version (set from nvrtcVersion at load time),
60+
// so the major component is non-zero (e.g. 12 for CUDA 12.x).
61+
SLANG_CHECK(result == SLANG_OK);
62+
SLANG_CHECK(major > 0);
63+
64+
// Null out-params must be tolerated.
65+
SLANG_CHECK(
66+
globalSession->getDownstreamCompilerVersion(
67+
SLANG_PASS_THROUGH_NVRTC,
68+
nullptr,
69+
nullptr) == SLANG_OK);
70+
}
71+
else
72+
{
73+
SLANG_CHECK(result == SLANG_E_NOT_FOUND);
74+
}
75+
76+
// Exercise the documented "loaded but versionless" clause: a bundled, GPU-independent compiler
77+
// that loads but exposes no numeric version must still succeed with (0,0) — it must NOT be
78+
// conflated with SLANG_E_NOT_FOUND. glslang leaves getDesc().version at (0,0) and is loadable
79+
// on CI runners without a device.
80+
if (SLANG_SUCCEEDED(globalSession->checkPassThroughSupport(SLANG_PASS_THROUGH_GLSLANG)))
81+
{
82+
int gMajor = -1;
83+
int gMinor = -1;
84+
SLANG_CHECK(
85+
globalSession->getDownstreamCompilerVersion(
86+
SLANG_PASS_THROUGH_GLSLANG,
87+
&gMajor,
88+
&gMinor) == SLANG_OK);
89+
SLANG_CHECK(gMajor == 0 && gMinor == 0);
90+
}
91+
}

tools/slang-unit-test/unit-test-vtable-stability.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ SLANG_UNIT_TEST(vtableISlangProfiler)
665665
}
666666

667667
// ---------------------------------------------------------------------------
668-
// IGlobalSession : ISlangUnknown (own slots 3-31)
668+
// IGlobalSession : ISlangUnknown (own slots 3-32)
669669
// ---------------------------------------------------------------------------
670670
struct IGlobalSessionProbe : IGlobalSession
671671
{
@@ -841,6 +841,12 @@ struct IGlobalSessionProbe : IGlobalSession
841841
lastSlot = 31;
842842
return SLANG_OK;
843843
}
844+
SLANG_NO_THROW SlangResult SLANG_MCALL
845+
getDownstreamCompilerVersion(SlangPassThrough, int*, int*) SLANG_OVERRIDE
846+
{
847+
lastSlot = 32;
848+
return SLANG_OK;
849+
}
844850
};
845851

846852
SLANG_UNIT_TEST(vtableIGlobalSession)
@@ -862,6 +868,8 @@ SLANG_UNIT_TEST(vtableIGlobalSession)
862868
SLANG_CHECK(p.lastSlot == 26); // setSPIRVCoreGrammar
863869
callSlot(&p, 31);
864870
SLANG_CHECK(p.lastSlot == 31); // saveBuiltinModule
871+
callSlot(&p, 32);
872+
SLANG_CHECK(p.lastSlot == 32); // getDownstreamCompilerVersion
865873
}
866874

867875
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)