Skip to content

Add default value blob reflection API#11471

Open
duckdoom5 wants to merge 3 commits into
shader-slang:masterfrom
duckdoom5:reflect-default-value-blob
Open

Add default value blob reflection API#11471
duckdoom5 wants to merge 3 commits into
shader-slang:masterfrom
duckdoom5:reflect-default-value-blob

Conversation

@duckdoom5
Copy link
Copy Markdown

This PR adds a new API to reflect default initializer values. This follows DXC’s D3D12_SHADER_VARIABLE_DESC::DefaultValue style, retrieving default initializer data as an ISlangBlob.

This new API is intended to support the entire subset of default-able values. Unlike previous implementations, this also includes non-static const fields (like those in material constant-buffers). (See the unit tests for coverage).

Implementation notes:

  • Variables without explicit initializers return success with a nullptr blob, (modelled after DXC's behavior).
  • The existing scalar default-value helpers are kept for compatibility, but deprecated in favor of the blob API.
  • The blob stores values in natural scalar/field order and does not include constant-buffer padding.

Fixes #11106

Copilot AI review requested due to automatic review settings June 4, 2026 09:37
@duckdoom5 duckdoom5 requested a review from a team as a code owner June 4, 2026 09:37
@duckdoom5 duckdoom5 requested review from bmillsNV and removed request for a team June 4, 2026 09:37
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

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

This PR adds a blob-based reflection API to retrieve serialized default-initializer bytes for variables, deprecates legacy scalar accessors, implements serialization for all reflected types (scalars, special floats, vectors/matrices/arrays/structs/enums), and adds unit tests validating blob semantics and contents.

Changes

Default Value Blob Reflection API

Layer / File(s) Summary
API contract (C and C++ declarations)
include/slang-deprecated.h, include/slang.h
Declare spReflectionVariable_GetDefaultValueBlob; mark spReflectionVariable_HasDefaultValue, spReflectionVariable_GetDefaultValueInt, spReflectionVariable_GetDefaultValueFloat and C++ VariableReflection::hasDefaultValue/getDefaultValueInt/getDefaultValueFloat as deprecated; add VariableReflection::getDefaultValueBlob wrapper.
Core serialization implementation
source/slang/slang-reflection-api.cpp
Implement spReflectionVariable_GetDefaultValueBlob, helpers to unwrap initializers, encode scalars and special float formats, expand aggregates (vectors/matrices/arrays/structs), resolve enum tag values, and produce a ListBlob or null blob per documented semantics and error codes.
Validation tests
tools/slang-unit-test/unit-test-link-time-type-reflection.cpp, tools/slang-unit-test/unit-test-special-scalar-reflection.cpp
Add defaultValueBlobReflection test and update existing tests to fetch default-value blobs, assert null/non-null semantics, buffer sizes, and verify serialized contents across scalar, special-scalar, vector/matrix, array, struct, enum, and derived-uninitialized cases; add negative argument checks.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.36% 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 accurately describes the main change: adding a new blob-based reflection API for retrieving default initializer values.
Description check ✅ Passed The description clearly explains the new blob-based API, its modeling after DXC, deprecation of old scalar functions, and its application to non-static const fields.
Linked Issues check ✅ Passed The PR successfully implements the blob-style API requested in issue #11106 to retrieve default initializer values for vectors, matrices, and other types beyond scalar int/float.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the new blob-based default value reflection API and deprecating old scalar accessors; no out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Jun 4, 2026

CLA assistant check
All committers have signed the CLA.

@duckdoom5 duckdoom5 changed the title Reflect default value blob Add default value blob reflection API Jun 4, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a reflection API to retrieve default initializer values as raw bytes (“blob”) and validates it via a new unit test covering scalar/aggregate defaults.

Changes:

  • Added spReflectionVariable_GetDefaultValueBlob implementation to serialize default initializers into a byte blob.
  • Added C++ wrapper VariableReflection::getDefaultValueBlob() and deprecated older scalar default-value helpers.
  • Added a unit test to validate blob contents for scalars, vectors, matrices, arrays, structs, inheritance, and enums.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
tools/slang-unit-test/unit-test-link-time-type-reflection.cpp Adds a unit test that exercises the new default-value blob reflection API across many types.
source/slang/slang-reflection-api.cpp Implements expression/type walking to serialize initializer defaults into an ISlangBlob.
include/slang.h Exposes getDefaultValueBlob() and deprecates older default-value APIs in the C++ wrapper.
include/slang-deprecated.h Declares the new C API entry point alongside deprecated reflection functions.

Comment thread tools/slang-unit-test/unit-test-link-time-type-reflection.cpp
Comment thread include/slang-deprecated.h
Comment thread source/slang/slang-reflection-api.cpp
Comment thread source/slang/slang-reflection-api.cpp Outdated
Comment thread include/slang.h Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 523e61ab-a47e-486d-86a7-2be4e0866054

📥 Commits

Reviewing files that changed from the base of the PR and between eb0dba8 and 1615f2b.

📒 Files selected for processing (4)
  • include/slang-deprecated.h
  • include/slang.h
  • source/slang/slang-reflection-api.cpp
  • tools/slang-unit-test/unit-test-link-time-type-reflection.cpp

Comment thread include/slang-deprecated.h
Comment thread source/slang/slang-reflection-api.cpp Outdated
Comment thread source/slang/slang-reflection-api.cpp
Comment thread source/slang/slang-reflection-api.cpp
Comment thread source/slang/slang-reflection-api.cpp
Comment thread source/slang/slang-reflection-api.cpp
github-actions[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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

3341-3352: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle enum-case variables in getDefaultValueBlob to keep API contracts consistent.

At Line 3994, spReflectionVariable_GetDefaultValueBlob downcasts to VarDeclBase and rejects non-VarDeclBase values with SLANG_E_INVALID_ARG. But enum cases are also reflected as SlangReflectionVariable and spReflectionVariable_HasDefaultValue reports them as defaulted (Line 3348-Line 3352). This creates a contract break where HasDefaultValue() can return true while blob retrieval fails for the same reflected variable kind.

🔧 Suggested fix sketch
 SLANG_API SlangResult
 spReflectionVariable_GetDefaultValueBlob(SlangReflectionVariable* inVar, ISlangBlob** outBlob)
 {
     if (!inVar || !outBlob)
         return SLANG_E_INVALID_ARG;

     *outBlob = nullptr;

     auto var = convert(inVar);
+    auto decl = var.getDecl();
+
+    if (auto enumCaseDecl = as<EnumCaseDecl>(decl))
+    {
+        auto parentEnumDecl = as<EnumDecl>(enumCaseDecl->parentDecl);
+        if (!parentEnumDecl)
+            return SLANG_E_INVALID_ARG;
+
+        auto astBuilder = getModule(parentEnumDecl)->getLinkage()->getASTBuilder();
+        auto tagType = getTagType(astBuilder, DeclRef<EnumDecl>(parentEnumDecl));
+
+        List<uint8_t> defaultValueBytes;
+        if (!appendIntegerConstantDefaultValue(enumCaseDecl->tagVal, tagType, defaultValueBytes))
+            return SLANG_E_NOT_AVAILABLE;
+
+        *outBlob = ListBlob::moveCreate(defaultValueBytes).detach();
+        return SLANG_OK;
+    }
 
     auto varDeclRef = var.as<VarDeclBase>();
     auto varDecl = varDeclRef.getDecl();
     if (!varDecl || !varDecl->type.type)
         return SLANG_E_INVALID_ARG;

Also applies to: 3986-3997


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f04649bc-9def-43a7-ad9b-e57fbee991c3

📥 Commits

Reviewing files that changed from the base of the PR and between 1615f2b and 65ad6b6.

📒 Files selected for processing (5)
  • include/slang-deprecated.h
  • include/slang.h
  • source/slang/slang-reflection-api.cpp
  • tools/slang-unit-test/unit-test-link-time-type-reflection.cpp
  • tools/slang-unit-test/unit-test-special-scalar-reflection.cpp

github-actions[bot]

This comment was marked as outdated.

@duckdoom5 duckdoom5 force-pushed the reflect-default-value-blob branch from bda158b to 3e4fd53 Compare June 4, 2026 13:00
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (5)
source/slang/slang-reflection-api.cpp (5)

3993-3997: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Use a checked decl cast before accessing VarDeclBase fields.

Line 3994 rebinds via var.as<VarDeclBase>(), which does not validate the underlying decl kind. If a non-VarDeclBase handle reaches this API, dereferencing varDecl->type at Line 3996 is undefined behavior.

🔧 Proposed fix
-    auto var = convert(inVar);
-    auto varDeclRef = var.as<VarDeclBase>();
-    auto varDecl = varDeclRef.getDecl();
+    auto var = convert(inVar);
+    auto varDecl = as<VarDeclBase>(var.getDecl());
     if (!varDecl || !varDecl->type.type)
         return SLANG_E_INVALID_ARG;
+    auto varDeclRef = var.as<VarDeclBase>();

3602-3611: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Guard module/linkage lookups before dereferencing AST builder.

Line 3602 (and similarly Lines 3841, 3909, 3974) assumes getModule(...)->getLinkage() is always non-null. These are crash paths if a decl is unparented or reflected from partial state.

🔧 Proposed hardening pattern
-    auto astBuilder = getModule(varDecl)->getLinkage()->getASTBuilder();
+    auto module = getModule(varDecl);
+    if (!module)
+        return false;
+    auto linkage = module->getLinkage();
+    if (!linkage)
+        return false;
+    auto astBuilder = linkage->getASTBuilder();
+    SLANG_AST_BUILDER_RAII(astBuilder);

Also applies to: 3841-3841, 3909-3910, 3974-3975


3907-3917: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Do not silently zero enum defaults for non-DeclRefExpr initializers.

When the enum initializer is a valid non-DeclRefExpr (e.g. cast/literal form), Line 3916 falls back to default construction and returns zero bytes with success semantics, which corrupts reflected defaults.

🔧 Proposed fix
         if (auto enumDeclRef = declRefType->getDeclRef().as<EnumDecl>())
         {
             auto astBuilder = getModule(enumDeclRef.getDecl())->getLinkage()->getASTBuilder();
             auto tagType = getTagType(astBuilder, enumDeclRef);
             if (auto declRefExpr = as<DeclRefExpr>(expr))
             {
                 if (appendEnumDefaultValue(declRefExpr->declRef, tagType, outBytes))
                     return true;
             }
-            return appendDefaultConstructedValue(tagType, outBytes);
+            return appendDefaultValue(tagType, expr, outBytes);
         }

3659-3716: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Coerce BoolLiteralExpr for numeric scalar targets.

appendScalarDefaultValue captures boolLiteral, but only the BaseType::Bool branch uses it. Numeric targets (int, float, intptr_t, etc.) reject true/false after cast unwrapping, returning SLANG_E_NOT_AVAILABLE for legal initializers.

🔧 Proposed fix
     auto intLiteral = as<IntegerLiteralExpr>(expr);
     auto floatLiteral = as<FloatingPointLiteralExpr>(expr);
     auto boolLiteral = as<BoolLiteralExpr>(expr);
+    IntegerLiteralExpr syntheticIntLiteral;
+    if (!intLiteral && boolLiteral)
+    {
+        syntheticIntLiteral.value = boolLiteral->value ? 1 : 0;
+        intLiteral = &syntheticIntLiteral;
+    }

3788-3798: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Cap serialized default blob size to avoid OOM on large arrays.

Array serialization/default-construction loops are bounded only by declared constant element count. Large valid constants can force massive allocations and stall or OOM reflection callers.

Also applies to: 3818-3824, 3964-3967


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 15e9b5b2-bce0-4854-836e-348ce1df7c3e

📥 Commits

Reviewing files that changed from the base of the PR and between 65ad6b6 and 3e4fd53.

📒 Files selected for processing (5)
  • include/slang-deprecated.h
  • include/slang.h
  • source/slang/slang-reflection-api.cpp
  • tools/slang-unit-test/unit-test-link-time-type-reflection.cpp
  • tools/slang-unit-test/unit-test-special-scalar-reflection.cpp

github-actions[bot]

This comment was marked as outdated.

@duckdoom5 duckdoom5 force-pushed the reflect-default-value-blob branch from 3e4fd53 to fd2496f Compare June 4, 2026 13:39
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
source/slang/slang-reflection-api.cpp (1)

3872-3875: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not treat 1-element initializer-list as vector splat.

At Line 3872, { 4.0f } and float3(4.0f) are handled identically, but initializer-list semantics should zero-fill remaining elements instead of splatting.

🔧 Proposed fix
 static bool appendVectorDefaultValue(VectorExpressionType* vectorType, Expr* expr, List<uint8_t>& outBytes)
 {
     Index elementCount = 0;
     if (!getDefaultValueKnownCount(vectorType->getElementCount(), elementCount))
         return false;

     auto elementType = vectorType->getElementType();
     const auto argCount = getDefaultValueArgCount(expr);
     if (argCount == 0)
         return appendRepeatedScalarDefaultValue(elementType, expr, elementCount, outBytes);

-    if (argCount == 1 && elementCount > 1)
+    const bool isInitializerList = as<InitializerListExpr>(unwrapDefaultValueExpr(expr)) != nullptr;
+    if (!isInitializerList && argCount == 1 && elementCount > 1)
         return appendRepeatedScalarDefaultValue(elementType, getDefaultValueArg(expr, 0), elementCount, outBytes);

     return appendSequentialDefaultValue(elementType, elementCount, expr, outBytes);
 }
tools/slang-unit-test/unit-test-link-time-type-reflection.cpp (1)

287-290: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit bool false / 0 blob assertions.

Current bool checks validate only true and 1. Please also pin false and 0 so both bool-encoding branches are covered.

🔧 Proposed test additions
             public static const bool ScalarBool = true;
             public static const bool ScalarBoolFromInt = 1;
+            public static const bool ScalarBoolFalse = false;
+            public static const bool ScalarBoolFromIntZero = 0;
             public static const int ScalarIntFromBool = true;
             public static const float ScalarFloatFromBool = false;
     auto scalarBoolFromInt = getDefaultBlob("ScalarBoolFromInt");
     SLANG_CHECK(scalarBoolFromInt->getBufferSize() == sizeof(uint32_t));
     SLANG_CHECK(((const uint32_t*)scalarBoolFromInt->getBufferPointer())[0] == 1);

+    auto scalarBoolFalse = getDefaultBlob("ScalarBoolFalse");
+    SLANG_CHECK(scalarBoolFalse->getBufferSize() == sizeof(uint32_t));
+    SLANG_CHECK(((const uint32_t*)scalarBoolFalse->getBufferPointer())[0] == 0);
+
+    auto scalarBoolFromIntZero = getDefaultBlob("ScalarBoolFromIntZero");
+    SLANG_CHECK(scalarBoolFromIntZero->getBufferSize() == sizeof(uint32_t));
+    SLANG_CHECK(((const uint32_t*)scalarBoolFromIntZero->getBufferPointer())[0] == 0);
+
     auto scalarIntFromBool = getDefaultBlob("ScalarIntFromBool");

Also applies to: 409-416


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4a6f98d5-9326-4fff-b9d6-b692bca9c2cd

📥 Commits

Reviewing files that changed from the base of the PR and between 3e4fd53 and fd2496f.

📒 Files selected for processing (5)
  • include/slang-deprecated.h
  • include/slang.h
  • source/slang/slang-reflection-api.cpp
  • tools/slang-unit-test/unit-test-link-time-type-reflection.cpp
  • tools/slang-unit-test/unit-test-special-scalar-reflection.cpp

Comment thread include/slang-deprecated.h
Comment thread source/slang/slang-reflection-api.cpp
github-actions[bot]

This comment was marked as outdated.

@duckdoom5 duckdoom5 force-pushed the reflect-default-value-blob branch from fd2496f to 0753056 Compare June 4, 2026 15:38
github-actions[bot]

This comment was marked as outdated.

@duckdoom5 duckdoom5 force-pushed the reflect-default-value-blob branch from 0753056 to 0ebaa77 Compare June 4, 2026 16:40
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Verdict: 🔴 Has issues — 1 bug, 4 gaps, 1 question

The new spReflectionVariable_GetDefaultValueBlob API has a recurring weakness in how it enforces kMaxDefaultValueBlobBytes: the byte-cap is checked only after each recursive append, the element-count gate compares element count against a byte-count constant, and appendRepeatedScalarDefaultValue skips the in-loop capacity check entirely. Together these allow blob growth several multiples past the documented 16 MB cap. Recursion depth is bounded only for enum-alias indirection — not for struct/array nesting — so deeply nested types can stack-overflow before the byte cap engages. Tests cover scalar, vector, matrix, and flat-aggregate happy paths well; nested aggregates (array-of-struct, struct-with-array, byte-cap-only overflow, deep enum chain) are unexercised.

Changes Overview

New blob reflection API (include/slang-deprecated.h, include/slang.h, source/slang/slang-reflection-api.cpp)

  • Adds spReflectionVariable_GetDefaultValueBlob (C) and VariableReflection::getDefaultValueBlob (C++) returning an ISlangBlob* of scalar-layout bytes for a variable's default initializer. Bool emitted as 4-byte uint32, matrices row-by-row, struct base fields before derived, enums in the underlying tag type, no aggregate padding. Returns SLANG_OK with null blob when there is no initializer; SLANG_E_NOT_AVAILABLE when an initializer cannot be represented.
  • Marks hasDefaultValue / getDefaultValueInt / getDefaultValueFloat (and their C counterparts) SLANG_DEPRECATED and points doc comments at the new API.

Test additions (tools/slang-unit-test/unit-test-link-time-type-reflection.cpp, tools/slang-unit-test/unit-test-special-scalar-reflection.cpp)

  • New defaultValueBlobReflection test fixture covering all scalar base types, vectors with full/splat/partial/empty initializers, matrices, flat arrays, struct partial/default, derived struct, enums (value/alias/cast/default/uint8 tag), EnumCaseDecl direct lookup, no-initializer-null-blob, both null-arg paths, and a 16M+1-element overflow case.
  • specialScalarReflection extended to verify BFloat16 / FloatE4M3 / FloatE5M2 default-value blobs.
Findings (6 total)
Severity Location Finding
🔴 Bug source/slang/slang-reflection-api.cpp:3823 appendRepeatedScalarDefaultValue loop never checks hasDefaultValueBlobCapacity; allows ~128 MB splats past the 16 MB cap
🟡 Gap source/slang/slang-reflection-api.cpp:3631 getDefaultValueKnownCount compares element count to a byte-count constant (kMaxDefaultValueBlobBytes), so the gate is element-size-blind
🟡 Gap source/slang/slang-reflection-api.cpp:3848 Capacity check inside appendSequentialDefaultValue runs after the recursive append; nested aggregates compound the per-level overshoot
🟡 Gap source/slang/slang-reflection-api.cpp:3793 Only enum indirection has a recursion-depth limit; struct/array nesting can stack-overflow before the byte cap engages
🟡 Gap tools/slang-unit-test/unit-test-link-time-type-reflection.cpp:419 No tests for array-of-struct / struct-with-array / byte-cap-only overflow / deep enum chain
🔵 Question include/slang-deprecated.h:741 New non-deprecated C declaration is placed in slang-deprecated.h, contradicting the file's documented purpose

Comment thread source/slang/slang-reflection-api.cpp
Comment thread source/slang/slang-reflection-api.cpp
Comment thread source/slang/slang-reflection-api.cpp
Comment thread source/slang/slang-reflection-api.cpp
Comment thread include/slang-deprecated.h
Comment thread tools/slang-unit-test/unit-test-link-time-type-reflection.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

More default initializer value getters in reflection

3 participants