Skip to content

Unify associatedtype constraint representation; make autodiff requirement access order-agnostic#11368

Open
csyonghe wants to merge 3 commits into
shader-slang:masterfrom
csyonghe:derived-interface-constraint
Open

Unify associatedtype constraint representation; make autodiff requirement access order-agnostic#11368
csyonghe wants to merge 3 commits into
shader-slang:masterfrom
csyonghe:derived-interface-constraint

Conversation

@csyonghe
Copy link
Copy Markdown
Collaborator

1. Motivation

Slang lets you bound an associated type in two ways that ought to be exactly equivalent:

interface I { associatedtype A : IFoo; }          // inheritance-clause bound
interface I { associatedtype A where A : IFoo; }   // where-clause bound

They were not modeled the same way internally (the : bound was nested inside the
AssocTypeDecl; a where bound was a separate constraint), which meant the two forms could
behave differently and there was no single, principled representation for "a constraint on an
associated type." We also had no surface syntax to constrain an inherited associated type from a
derived interface.

This PR makes the two forms identical and adds a __constraint form for derived interfaces, e.g.:

interface IBase  { associatedtype DataType; }
interface IDerived : IBase { __constraint DataType == This; }   // for T:IDerived, T.DataType == T

Example tests added:

  • tests/language-feature/interfaces/derived-interface-constraint.slang__constraint DataType == This, checked via the interpreter (expects 42).
  • tests/language-feature/interfaces/derived-interface-constraint-deep.slang — multi-level chain (This.A.A == ...), expects 15.
  • tests/language-feature/interfaces/derived-interface-constraint-error.slang — diagnostic test for a type that violates the constraint (error E38029).

2. Proposed solution

Model every associated-type constraint — associatedtype A : IFoo, associatedtype A where A : IFoo, and __constraint A : IFoo / A == B — identically: as a constraint requirement of the enclosing interface, a sibling of the associated type.

This mirrors how a generic function void foo<T : IFoo>() represents the parameter T and the
constraint T : IFoo as parallel members of the enclosing generic. An associated type and a
constraint on it are likewise both requirements of the interface.

Making the built-in differentiable interfaces (IDifferentiable, IBackwardDifferentiable,
IBwdCallable, …) survive this relocation required removing the two places where auto-diff relied
on the old layout:

  • Built-in interface requirements were addressed by key identity (and a key could be minted
    more than once for the same logical requirement) → replaced with a hoistable
    IRBuiltinRequirementKey
    deduplicated by construction from its role.
  • The auto-diff backend read requirements by hardcoded operand index → replaced with
    role-based lookup.

3. Change summary

Area Files What
Parser slang-parser.cpp __constraint decl + keyword; parseAssocType routes the : bound and the where clause to the enclosing interface.
Conformance check slang-check-decl.cpp, slang-check-inheritance.cpp, slang-ast-decl-ref.cpp Validate an interface-level constraint requirement; surface T.A's constraints to generic code; resolve the differential rule by the constraint subject.
IR: built-in requirement keys slang-ir-insts.lua, slang-ir-insts-stable-names.lua, slang-ir-insts.h, slang-ast-support-types.h New hoistable IRBuiltinRequirementKey(kind) + IRBuiltinRequirementDecoration; BuiltinRequirementKind += DifferentialWitness, DifferentialPtrWitness, BwdCallableContextWitness.
IR lowering slang-lower-to-ir.cpp getInterfaceRequirementKey returns the hoistable key for built-in roles; addEntry lowers a relocated subtype constraint with a WitnessTableType value.
Linking / emit slang-ir-link.cpp, slang-emit-c-like.cpp Keep built-in keys out of mangled-name/linkage logic (eager-clone, skip in emit).
Auto-diff backend slang-ir-autodiff.{h,cpp}, slang-ir-autodiff-fwd.cpp Address IDifferentiable/IBackwardDifferentiable/IBwdCallable requirements by role; remove dead index-based accessor and brittle count asserts.
Tests 3 × tests/language-feature/interfaces/derived-interface-constraint*.slang feature + deep-chain + diagnostic.

Verification: core module compiles with assertions active; tests/autodiff/,
tests/autodiff-dstdlib/, tests/language-feature/{interfaces,generics,associated-types}/, all
*where* tests, and the 3 new tests have 0 non-environmental failures (the only failures are
the syn (llvm) host-execution variants, which fail identically for unrelated trivial tests in
this CI-less sandbox).

4. How this set of changes was derived (every change, with reasons and code traces)

This started as "add derived-interface constraints," became "unify all associated-type
constraints," and the bulk of the work was chasing a chain of cascading auto-diff failures that the
unification exposed. Each change below is justified by a concrete failure it fixes.

4.1 Parser — one representation for all three forms

parseAssocType sends both the inheritance clause (parseOptionalGenericConstraints(..., constraintTarget = interface)) and the where clause (maybeParseGenericConstraints(..., interface)) to the enclosing interface, and __constraint (parseInterfaceConstraintDecl) builds
the same GenericTypeConstraintDecl directly under the interface. Without this, : IFoo and
where A : IFoo remain different ASTs and the equivalence the PR promises does not hold.

4.2 Conformance checking and surfacing

  • findWitnessForInterfaceRequirement (slang-check-decl.cpp) gains a branch for a
    GenericTypeConstraintDecl requirement: it proves the constraint holds for the conforming type
    via isSubtype (requiring a type-equality witness for ==), and diagnoses
    TypeArgumentDoesNotConformToInterface otherwise. Without it, a type "conforming" to
    IDerived whose DataType != This would be accepted (unsound), and the constraint would be
    reported as an unsatisfied named member.
  • slang-check-inheritance.cpp surfaces the relocated constraint to generic code: for an access
    T.A, it walks T's interface facets and adds the constraint's other endpoint as a base of
    T.A. Without it, f<T:IDerived>() does not know T.DataType == T ('value' is not a member of 'T.DataType'). Includes a cycle guard (skip isBeingChecked()) and a depth guard (only the
    reduce-to-shallower direction of a symmetric equality is added, else self-referential equalities
    such as Differential.Differential == Differential recurse forever — this crashed while compiling
    where-bounded vector<T,N>).
  • LookupDeclRef::tryResolve (slang-ast-decl-ref.cpp) answers "which associated type does this
    constraint constrain?" from the constraint subject rather than assuming the parent decl is the
    associated type. Without it, auto-diff's doesSignatureMatchRequirement could not reduce
    X.Differential and asserted.

4.3 The cascade: relocating the built-in differentiable bounds

The interesting part. Relocating associatedtype Differential : IDifferentiable (and the backward
analogues) to interface scope broke auto-diff in a sequence of distinct ways. Each was root-caused
to an assumption that built-in interface requirements are identified by position or key
identity
rather than by role.

(a) Missing / mismatched DifferentialWitness → crash on tests/autodiff/deduplicate-witness-table.slang.
At the IR level specializeWitnessLookupfindWitnessTableEntry(table, key) matches by pointer
identity
. getInterfaceRequirementKey minted a fresh, linkage-named IRStructKey per
requirement decl
, so the canonical interface constraint and a constraint synthesized while building
a type's Differential produced two distinct keys for the same role → the lookup missed →
replaceUsesWith(null) tripped SLANG_ASSERT(other) (and leaked / crashed with 0xC0000005).

Fix: a new hoistable inst IRBuiltinRequirementKey(kind) (one IRIntLit operand). Because it
is hoistable it is deduplicated by construction from its kind operand, so every reference to a
given built-in requirement resolves to a single key inst — across decls and across the precompiled
core-module boundary. getInterfaceRequirementKey returns it for built-in roles: an explicit
__builtin_requirement(kind), or a derived witness role for an associated type's : I bound
(DifferentialType → DifferentialWitness, DifferentialPtrType → DifferentialPtrWitness,
BwdCallableContextType → BwdCallableContextWitness). This is the right fix (not a lookup
work-around) because a built-in requirement is identified by its role; making "same role ⇒ same
key" hold by construction is what witness-table lookup-by-identity assumes.

It can't be a subclass of IRStructKeyStructKey is a concrete instantiable leaf, and making it
a parent removes its kIROp_StructKey opcode. So the requirement-key type is widened
IRStructKey* → IRInst* through getInterfaceRequirementKey, its cache, and the addEntry lambda;
the auto-diff context members that hold these keys become IRInst* (they are only ever used as
lookup keys, which accept any IRInst*).

(b) Built-in keys must stay out of linkage (per design: they have no mangled name). Two link-time
sites keyed deferred witness-table-entry cloning by getMangledName(key), which is empty for a
hoistable key — multiple built-in entries would collide on "". Fix (slang-ir-link.cpp):
built-in-requirement witness entries are cloned eagerly (skipping the mangled-name-keyed deferred
dictionary), and a LookupWitnessMethod on a built-in key does not record an empty deferred key.
Fix (slang-emit-c-like.cpp): ensureGlobalInst skips kIROp_BuiltinRequirementKey (it is
metadata, like an interface requirement entry, and being hoistable may survive unreferenced).
Without these, the build asserted on a duplicate "" dictionary key, and emit hit
diagnoseUnhandledInst ("unexpected IR opcode during code emit").

(c) Relocated subtype constraint lowered with the wrong requirement value. When the bound was
nested, interface lowering produced a conformance-witness requirement entry whose
requirementVal is a WitnessTableType. After relocation the constraint is a sibling member
handled by addEntry, which only special-cased InheritanceDecl and so gave a relocated
GenericTypeConstraintDecl a malformed value. Fix (slang-lower-to-ir.cpp): addEntry lowers a
relocated subtype constraint exactly like the nested bound (a WitnessTableType value);
equality constraints keep the generic path. Without it, tests/autodiff/high-order-backward-diff-1.slang
crashed in specializeWitnessLookup because the conformance entry's value was not a witness table.

(d) The auto-diff backend read requirements by hardcoded operand index. This was the root of the
remaining backward-diff / higher-order / cross-module / extension regressions (e.g.
high-order-backward-diff-1/2, autopybind-*, cuda-kernel-export, func-extension/fwd-diff-*).
slang-ir-autodiff-fwd.cpp read IBackwardDifferentiable/IBwdCallable requirement keys with
cast<IRInterfaceRequirementEntry>(iface->getOperand(N))->getRequirementKey() (≈13 sites, guarded
by SLANG_ASSERT(getRequirementCount() == 6/3/1)). Relocation restructured the
BwdCallable : IBwdCallable conformance requirement, shifting/altering these positions → malformed
witness tables and a null getFuncDefinitionForContext in type-flow specialization. Fix: expose
getInterfaceEntryByBuiltinRequirement(interface, kind) and convert both the consuming block and the
witness-table-building block to role lookup; drop the brittle count asserts; delete the now-dead
getInterfaceEntryAtIndex. This is the principled fix (and the one this PR is built around):
witness-table / interface requirements are an unordered key→value collection, so a consumer must
address them by role, never by position. (IForwardDifferentiable's accesses are intentionally left
index-based: its op1/op2 are fwd_diff's callable conformance constraints, which are not
relocated and are layout-stable.)

After (a)–(d), the full auto-diff suite and the language-feature suites are green with zero
non-environmental failures, and associatedtype A : IFoo and associatedtype A where A : IFoo are
genuinely the same representation.

🤖 Generated with Claude Code

…ment access order-agnostic

Model `associatedtype A : IFoo` identically to `associatedtype A where A : IFoo`:
both surface forms relocate to the enclosing interface as sibling constraint
requirements (mirroring how a generic parameter and its constraint are parallel
members of the enclosing generic). Add the derived-interface `__constraint`
form that produces the same representation.

To make this work for the built-in differentiable interfaces, introduce a
hoistable `IRBuiltinRequirementKey(kind)` so built-in interface requirements are
deduplicated by role (not by name/identity), and convert the auto-diff backend
to address `IBackwardDifferentiable`/`IBwdCallable` requirements by role instead
of by hardcoded operand index.
@csyonghe csyonghe requested a review from a team as a code owner May 30, 2026 00:51
@csyonghe csyonghe requested review from bmillsNV and Copilot and removed request for a team May 30, 2026 00:51
@csyonghe csyonghe added the pr: non-breaking PRs without breaking changes label May 30, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 30, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Implements interface-level __constraint parsing and semantics, represents builtin requirement roles in IR via hoistable keys/decoration, surfaces interface constraints during inheritance/witness lookup, and migrates autodiff/witness-table addressing from operand-indexing to BuiltinRequirementKind role-based lookups.

Changes

Interface-Level Constraints

Layer / File(s) Summary
IR foundation for builtin requirement keys
source/slang/slang-ir-insts.h, source/slang/slang-ir-insts.lua, source/slang/slang-ir-insts-stable-names.lua
New IRBuiltinRequirementKey hoistable instruction and IRBuiltinRequirementDecoration metadata, along with IRBuilder::getBuiltinRequirementKey() and IRBuilder::addBuiltinRequirementDecoration() APIs to create and attach builtin requirement role identifiers.
Parser support for interface constraints
source/slang/slang-parser.cpp
New parseInterfaceConstraintDecl for __constraint ... == ...; and __constraint ... : ...; syntax. Parser routes associated-type constraints to enclosing InterfaceDecl via a configurable constraintTarget and registers __constraint.
Semantic checking and constraint inheritance
source/slang/slang-check-decl.cpp, source/slang/slang-check-inheritance.cpp
findWitnessForInterfaceRequirement validates and installs witnesses for GenericTypeConstraintDecl interface-level requirements. _calcInheritanceInfo now surfaces and matches interface-level constraints for associated-type accesses and filters interface __constraint members by bare This subject.
IR lowering with builtin requirement detection
source/slang/slang-lower-to-ir.cpp
getInterfaceRequirementKey() now returns IRInst*, detects builtin requirement roles from BuiltinRequirementModifier or constrained associated-type roles, emits/gets hoistable builtin keys, attaches decorations, caches IRInst* keys, and lowers interface-level constraint entries as WitnessTableType values when appropriate.
Autodiff metadata initialization with kind-based lookups
source/slang/slang-ir-autodiff.h, source/slang/slang-ir-autodiff.cpp, source/slang/slang-ast-support-types.h
Declares/implements getInterfaceEntryByBuiltinRequirement(...) and changes AutoDiffSharedContext members from IRStructKey* to IRInst*; adds new BuiltinRequirementKind entries used for autodiff requirement/witness roles.
Autodiff witness table synthesis with role-based keys
source/slang/slang-ir-autodiff-fwd.cpp
maybeTranslateBackwardDerivativeWitness and emitCalleeAnnotationsForHigherOrderDiff now construct witness-table entries using getInterfaceEntryByBuiltinRequirement(..., BuiltinRequirementKind::...) lookups instead of fixed operand positions.
Linking and emission of builtin requirement keys
source/slang/slang-ast-decl-ref.cpp, source/slang/slang-ir-link.cpp, source/slang/slang-emit-c-like.cpp
registerClonedValue and cloneWitnessTableImpl avoid deferred bookkeeping for IRBuiltinRequirementKey entries and clone them eagerly when deep cloning is disabled; emission skips kIROp_BuiltinRequirementKey globals; decl-ref resolution recognizes constraints carried by GenericTypeConstraintDecl keys.
IR consumers: dispatch, dynamic-dispatch lowering, typeflow
source/slang/slang-ir-lower-dynamic-dispatch-insts.cpp, source/slang/slang-ir-typeflow-specialize.cpp
Widen lookupKey and similar fields to IRInst* and read witness lookup operands without casting to IRStructKey* to accept builtin requirement keys.
Language feature tests
tests/language-feature/interfaces/derived-interface-constraint.slang, tests/language-feature/interfaces/derived-interface-constraint-deep.slang, tests/language-feature/interfaces/derived-interface-constraint-error.slang
Tests exercise __constraint syntax, constraint inheritance across derived interfaces, and diagnostics for violations.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% 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 summarizes the main changes: unifying associatedtype constraint representation and making autodiff requirement access order-agnostic, which aligns with the substantial refactoring across parser, conformance checking, and IR layers.
Description check ✅ Passed The description comprehensively explains the motivation, proposed solution, detailed change summary with a table, and thorough justification for each change, including cascading fixes for autodiff failures—directly related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

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

This PR unifies the internal representation of associated-type constraints so that associatedtype A : IFoo, associatedtype A where A : IFoo, and derived-interface __constraint declarations all lower/check as interface-level requirements. It also updates the IR + autodiff pipeline to identify built-in interface requirements by role (via a hoistable IRBuiltinRequirementKey) rather than by decl identity or operand order, and adds regression tests for derived-interface associated-type constraints.

Changes:

  • Parser: route associated-type :/where constraints to the enclosing interface and add __constraint interface-body declarations.
  • Front-end + IR: validate and surface interface-level constraints, introduce hoistable built-in requirement keys, and lower relocated subtype constraints with correct witness-table-typed requirement values.
  • Autodiff: replace index-based interface requirement access with role-based lookup and adjust linking/emission behavior for built-in requirement keys; add new interface constraint tests.

Reviewed changes

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

Show a summary per file
File Description
tests/language-feature/interfaces/derived-interface-constraint.slang New interpreter test demonstrating __constraint DataType == This enabling T.DataTypeT.
tests/language-feature/interfaces/derived-interface-constraint-deep.slang New interpreter test verifying constraints remain visible through multi-level interface inheritance.
tests/language-feature/interfaces/derived-interface-constraint-error.slang New diagnostic test ensuring violated derived-interface constraints are rejected.
source/slang/slang-parser.cpp Adds __constraint parsing and unifies associated-type constraint attachment at interface scope.
source/slang/slang-check-decl.cpp Validates interface-level GenericTypeConstraintDecl requirements during conformance checking.
source/slang/slang-check-inheritance.cpp Surfaces interface-level constraints to associated-type accesses during inheritance/base computation.
source/slang/slang-ast-decl-ref.cpp Resolves built-in associated-type constraint identity from constraint subject (not parent decl).
source/slang/slang-ast-support-types.h Extends BuiltinRequirementKind with witness/conformance roles needed for relocated constraints.
source/slang/slang-ir-insts.lua Adds BuiltinRequirementKey and BuiltinRequirementDecoration IR constructs.
source/slang/slang-ir-insts.h Implements IR builder support for built-in requirement keys + decoration.
source/slang/slang-ir-insts-stable-names.lua Assigns stable-name IDs for the new IR opcode/decoration.
source/slang/slang-lower-to-ir.cpp Switches interface requirement key caching to IRInst*, creates hoistable built-in keys, and fixes lowering for relocated subtype constraints.
source/slang/slang-ir-link.cpp Prevents built-in requirement keys from entering mangled-name keyed deferred cloning; eagerly clones their witness-table entries.
source/slang/slang-emit-c-like.cpp Skips emitting kIROp_BuiltinRequirementKey as metadata-only global insts.
source/slang/slang-ir-autodiff.h Updates autodiff shared context key types to IRInst* and declares role-based entry lookup helper.
source/slang/slang-ir-autodiff.cpp Uses role-based requirement entry lookup when initializing autodiff shared context.
source/slang/slang-ir-autodiff-fwd.cpp Replaces index-based backward-diff requirement access with role-based lookup in forward-diff lowering.

Comment on lines +253 to +265
auto diffTypeEntry = getInterfaceEntryByBuiltinRequirement(
differentiableInterfaceType,
BuiltinRequirementKind::DifferentialType);
auto diffWitnessEntry = getInterfaceEntryByBuiltinRequirement(
differentiableInterfaceType,
BuiltinRequirementKind::DifferentialWitness);
auto zeroEntry = getInterfaceEntryByBuiltinRequirement(
differentiableInterfaceType,
BuiltinRequirementKind::DZeroFunc);
auto addEntry = getInterfaceEntryByBuiltinRequirement(
differentiableInterfaceType,
BuiltinRequirementKind::DAddFunc);

Comment on lines +300 to 313
auto ptrTypeEntry = getInterfaceEntryByBuiltinRequirement(
differentiablePtrInterfaceType,
BuiltinRequirementKind::DifferentialPtrType);
auto ptrWitnessEntry = getInterfaceEntryByBuiltinRequirement(
differentiablePtrInterfaceType,
BuiltinRequirementKind::DifferentialPtrWitness);

differentialAssocRefTypeStructKey = ptrTypeEntry->getRequirementKey();
differentialAssocRefTypeWitnessStructKey = ptrWitnessEntry->getRequirementKey();
differentialAssocRefTypeWitnessTableType =
cast<IRWitnessTableType>(ptrWitnessEntry->getRequirementVal());

isPtrInterfaceAvailable = true;
}
Comment on lines +925 to +929
auto bwdDiffContextTypeReqKey = getInterfaceEntryByBuiltinRequirement(
bwdDiffInterfaceType,
BuiltinRequirementKind::BwdCallableContextType)
->getRequirementKey();

Comment on lines +951 to +954
auto bwdCallableOpReqKey = getInterfaceEntryByBuiltinRequirement(
bwdCallableInterfaceType,
BuiltinRequirementKind::BwdCallablePropFunc)
->getRequirementKey();
Comment on lines 3558 to 3564
builder.createWitnessTableEntry(
newWitnessTable,
as<IRInterfaceRequirementEntry>(baseConformanceType->getOperand(0))->getRequirementKey(),
getInterfaceEntryByBuiltinRequirement(
baseConformanceType,
BuiltinRequirementKind::BwdCallableContextType)
->getRequirementKey(),
contextType);
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: 3

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-lower-to-ir.cpp (1)

11708-11749: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep interface equality constraints witness-table typed.

Line 11729 falls back to ensureDecl() for direct interface GenericTypeConstraintDecls. For __constraint A == B, that returns the requirement key, so the entry ends up with KeyType instead of WitnessTableType(B), and removeLinkageDecorations() mutates the key itself. Equality constraints should take the same witness-table path as the relocated : constraints.

Proposed fix
-            else if (
-                relocatedSubtypeConstraint &&
-                !relocatedSubtypeConstraint.getDecl()->isEqualityConstraint)
+            else if (relocatedSubtypeConstraint)
             {
-                // A subtype constraint on an associated type (`associatedtype A : IBar`,
-                // `associatedtype A where A : IBar`) is recorded in the unified
-                // representation as an interface-level requirement (a sibling of `A`). It is
-                // a *conformance* requirement: its witness is a witness table for the bound.
-                // We must lower it the same way the conformance was lowered when the bound was
-                // nested in the associated type (see the associated-type constraint loop
-                // below), i.e. with a `WitnessTableType` requirement value -- otherwise
-                // consumers (e.g. autodiff) that read the requirement get a malformed entry.
-                // (Equality constraints, e.g. `__constraint A == B`, are handled by the
-                // generic path below.)
+                // Interface-level type constraints are witness-valued requirements.
+                // Equality constraints also lower to a `TypeEqualityWitness`, whose
+                // IR type is `WitnessTableType(sup)`.
                 auto irBaseType = lowerType(
                     subContext,
                     getSup(subContext->astBuilder, relocatedSubtypeConstraint));
                 entry->setRequirementVal(subBuilder->getWitnessTableType(irBaseType));
             }

As per coding guidelines, "IR pass correctness — ensure SSA form and type invariants are maintained."


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 288891e2-8f67-40c1-b931-a041f5df5ac1

📥 Commits

Reviewing files that changed from the base of the PR and between 8aee8c9 and ed5f754.

📒 Files selected for processing (17)
  • source/slang/slang-ast-decl-ref.cpp
  • source/slang/slang-ast-support-types.h
  • source/slang/slang-check-decl.cpp
  • source/slang/slang-check-inheritance.cpp
  • source/slang/slang-emit-c-like.cpp
  • source/slang/slang-ir-autodiff-fwd.cpp
  • source/slang/slang-ir-autodiff.cpp
  • source/slang/slang-ir-autodiff.h
  • source/slang/slang-ir-insts-stable-names.lua
  • source/slang/slang-ir-insts.h
  • source/slang/slang-ir-insts.lua
  • source/slang/slang-ir-link.cpp
  • source/slang/slang-lower-to-ir.cpp
  • source/slang/slang-parser.cpp
  • tests/language-feature/interfaces/derived-interface-constraint-deep.slang
  • tests/language-feature/interfaces/derived-interface-constraint-error.slang
  • tests/language-feature/interfaces/derived-interface-constraint.slang

Comment on lines +410 to +424
else if (auto constraintDecl = as<GenericTypeConstraintDecl>(requirementKey))
{
auto assocFromExp = [](TypeExp const& exp) -> AssocTypeDecl*
{
if (auto declRefType = as<DeclRefType>(exp.type))
return as<AssocTypeDecl>(declRefType->getDeclRef().getDecl());
return nullptr;
};
auto assoc = assocFromExp(constraintDecl->sub);
if (!assoc)
assoc = assocFromExp(constraintDecl->sup);
if (assoc)
{
builtinReq = assoc->findModifier<BuiltinRequirementModifier>();
isConstraint = true;
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Try both sides when recovering the builtin requirement.

This only falls back to sup when sub is not an associated type. If sub is a non-builtin assoc type and the builtin differential assoc type is on sup, the special-case returns nullptr, so A == Differential and Differential == A stop resolving equivalently.

Proposed fix
         else if (auto constraintDecl = as<GenericTypeConstraintDecl>(requirementKey))
         {
-            auto assocFromExp = [](TypeExp const& exp) -> AssocTypeDecl*
+            auto builtinReqFromExp = [](TypeExp const& exp) -> BuiltinRequirementModifier*
             {
                 if (auto declRefType = as<DeclRefType>(exp.type))
-                    return as<AssocTypeDecl>(declRefType->getDeclRef().getDecl());
+                    if (auto assoc = as<AssocTypeDecl>(declRefType->getDeclRef().getDecl()))
+                        return assoc->findModifier<BuiltinRequirementModifier>();
                 return nullptr;
             };
-            auto assoc = assocFromExp(constraintDecl->sub);
-            if (!assoc)
-                assoc = assocFromExp(constraintDecl->sup);
-            if (assoc)
+            builtinReq = builtinReqFromExp(constraintDecl->sub);
+            if (!builtinReq)
+                builtinReq = builtinReqFromExp(constraintDecl->sup);
+            if (builtinReq)
             {
-                builtinReq = assoc->findModifier<BuiltinRequirementModifier>();
                 isConstraint = true;
             }
         }

Comment on lines +176 to 195
IRInterfaceRequirementEntry* getInterfaceEntryByBuiltinRequirement(
IRInterfaceType* interface,
UInt index)
BuiltinRequirementKind kind)
{
SLANG_UNUSED(moduleInst);
if (interface)
if (!interface)
return nullptr;
for (UInt i = 0; i < interface->getOperandCount(); i++)
{
// Assume for now that IDifferentiable has exactly five fields.
// SLANG_ASSERT(interface->getOperandCount() == 5);
if (auto entry = as<IRInterfaceRequirementEntry>(interface->getOperand(index)))
return entry;
else
auto entry = as<IRInterfaceRequirementEntry>(interface->getOperand(i));
if (!entry)
continue;
if (auto decor =
entry->getRequirementKey()->findDecoration<IRBuiltinRequirementDecoration>())
{
SLANG_UNEXPECTED("IDifferentiable interface entry unexpected type");
if ((BuiltinRequirementKind)decor->getKind() == kind)
return entry;
}
}

return nullptr;
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast when a built-in requirement role is missing.

Returning nullptr here turns any lowering/pass-order mismatch into an unchecked null dereference at the new call sites (for example Line 266, Line 307, Line 925, and Line 3560). In core compiler code this helper should diagnose or hard-assert when the requested role is absent instead of silently returning null.

Suggested fix
 IRInterfaceRequirementEntry* getInterfaceEntryByBuiltinRequirement(
     IRInterfaceType* interface,
     BuiltinRequirementKind kind)
 {
-    if (!interface)
-        return nullptr;
+    SLANG_RELEASE_ASSERT(interface);
     for (UInt i = 0; i < interface->getOperandCount(); i++)
     {
         auto entry = as<IRInterfaceRequirementEntry>(interface->getOperand(i));
         if (!entry)
             continue;
         if (auto decor =
                 entry->getRequirementKey()->findDecoration<IRBuiltinRequirementDecoration>())
         {
             if ((BuiltinRequirementKind)decor->getKind() == kind)
                 return entry;
         }
     }
-    return nullptr;
+    SLANG_UNEXPECTED("missing builtin interface requirement entry");
 }
As per coding guidelines, `source/slang/**` must ensure "Null pointer safety and proper error handling via diagnostics."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
IRInterfaceRequirementEntry* getInterfaceEntryByBuiltinRequirement(
IRInterfaceType* interface,
UInt index)
BuiltinRequirementKind kind)
{
SLANG_UNUSED(moduleInst);
if (interface)
if (!interface)
return nullptr;
for (UInt i = 0; i < interface->getOperandCount(); i++)
{
// Assume for now that IDifferentiable has exactly five fields.
// SLANG_ASSERT(interface->getOperandCount() == 5);
if (auto entry = as<IRInterfaceRequirementEntry>(interface->getOperand(index)))
return entry;
else
auto entry = as<IRInterfaceRequirementEntry>(interface->getOperand(i));
if (!entry)
continue;
if (auto decor =
entry->getRequirementKey()->findDecoration<IRBuiltinRequirementDecoration>())
{
SLANG_UNEXPECTED("IDifferentiable interface entry unexpected type");
if ((BuiltinRequirementKind)decor->getKind() == kind)
return entry;
}
}
return nullptr;
}
IRInterfaceRequirementEntry* getInterfaceEntryByBuiltinRequirement(
IRInterfaceType* interface,
BuiltinRequirementKind kind)
{
SLANG_RELEASE_ASSERT(interface);
for (UInt i = 0; i < interface->getOperandCount(); i++)
{
auto entry = as<IRInterfaceRequirementEntry>(interface->getOperand(i));
if (!entry)
continue;
if (auto decor =
entry->getRequirementKey()->findDecoration<IRBuiltinRequirementDecoration>())
{
if ((BuiltinRequirementKind)decor->getKind() == kind)
return entry;
}
}
SLANG_UNEXPECTED("missing builtin interface requirement entry");
}

Comment on lines +4061 to +4078
static NodeBase* parseInterfaceConstraintDecl(Parser* parser, void*)
{
auto constraint = parser->astBuilder->create<GenericTypeConstraintDecl>();
parser->FillPosition(constraint);
constraint->sub = parser->ParseTypeExp();
Token constraintToken;
if (AdvanceIf(parser, TokenType::OpEql, &constraintToken))
{
constraint->isEqualityConstraint = true;
}
else
{
constraintToken = parser->ReadToken(TokenType::Colon);
}
constraint->loc = constraintToken.loc;
constraint->sup = parser->ParseTypeExp();
parser->ReadToken(TokenType::Semicolon);
return constraint;
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject __constraint outside interface bodies.

This accepts __constraint in any declaration context. Since GenericTypeConstraintDecl is not covered by the nesting-validation tables, misplaced uses won't get a direct parser diagnostic and will leak an invalid AST into later checking. Please guard this path to InterfaceDecl parents, or wire GenericTypeConstraintDecl into isDeclAllowed().

As per coding guidelines, source/slang/**: "(5) Null pointer safety and proper error handling via diagnostics."

github-actions[bot]

This comment was marked as outdated.

@slangbot
Copy link
Copy Markdown
Contributor

slangbot commented May 30, 2026

⚠️ IR Instruction Files Changed

This PR modifies IR instruction definition files. Please review if you need to update the following constants in source/slang/slang-ir.h:

  • k_minSupportedModuleVersion: Should be incremented if you're removing instructions or making breaking changes
  • k_maxSupportedModuleVersion: Should be incremented when adding new instructions

These version numbers help ensure compatibility between different versions of compiled modules.

csyonghe added 2 commits May 29, 2026 21:23
Modeling `associatedtype __Tag : __BuiltinIntegerType` as an interface-level
constraint requirement means the `__EnumType` conformance check now validates it.
The compiler-synthesized enum conformance did not supply that witness (a
long-standing TODO), so permitted-but-non-conforming tag types such as `bool`
were wrongly rejected.

Provide the `__Tag : __BuiltinIntegerType` witness in the enum conformance
synthesis (a real subtype witness for integer tags, a NoneWitness for `bool`),
and have the conformance check respect a witness already provided by synthesis
instead of re-deriving it.

Fixes the regression in tests/bugs/11043-enum-constant-wraparound.slang.
Dynamic-dispatch lowering read the witness-table requirement key of an
`IRGetTagForMappedSet` via `cast<IRStructKey>`. A built-in interface requirement
(e.g. an IDifferentiable/IBackwardDifferentiable method reached through dynamic
dispatch) now uses the hoistable `IRBuiltinRequirementKey`, which is not an
`IRStructKey`: with assertions enabled the cast aborted (Linux/macOS debug CI),
and in release the unchecked cast led to a segfault on the CPU dynamic-dispatch
autodiff tests.

The key is only used as a witness-table lookup key (`findWitnessTableEntry`) and
to read a name hint, both of which accept any `IRInst*`, so type it as `IRInst*`
in both `TypeFlowSpecializationContext::collectDispatchActions` and the
`GetTagForMappedSet` lowering.

Fixes the dynamic-dispatch/existential autodiff regressions
(diff-assoctype-generic-interface, dynamic-dispatch-*, dyn-interface-*,
dynamic-object-bwd-diff-*, existential-*, material/diff-falcor-*).
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: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 683cdcb6-769a-4bff-83f2-12dc663b442a

📥 Commits

Reviewing files that changed from the base of the PR and between d456e33 and dcd6c56.

📒 Files selected for processing (2)
  • source/slang/slang-ir-lower-dynamic-dispatch-insts.cpp
  • source/slang/slang-ir-typeflow-specialize.cpp

Comment on lines +6366 to +6372
// A witness-table requirement key. This is `IRInst*` rather than
// `IRStructKey*` because a built-in interface requirement (e.g. an
// `IDifferentiable`/`IBackwardDifferentiable` method reached through
// dynamic dispatch) uses the hoistable `IRBuiltinRequirementKey`, which is
// not an `IRStructKey`. It is only used below as a witness-table lookup key
// and to read a name hint, both of which accept any `IRInst*`.
IRInst* lookupKey = nullptr;
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Synchronize IR schema with widened lookup key type

This consumer now accepts builtin requirement keys (IRBuiltinRequirementKey) via IRInst*, but the IR schema still declares dispatcher lookup keys as IRStructKey (source/slang/slang-ir-insts.lua, Lines 3006-3019). That contract mismatch can violate IR type invariants when dynamic dispatch carries builtin requirement roles.

Suggested follow-up (cross-file contract fix)
--- a/source/slang/slang-ir-insts.lua
+++ b/source/slang/slang-ir-insts.lua
@@
-        operands = {{"witnessTableSet", "IRWitnessTableSet"}, {"lookupKey", "IRStructKey"}}
+        operands = {{"witnessTableSet", "IRWitnessTableSet"}, {"lookupKey", "IRInst"}}

As per coding guidelines, "source/slang/: This is the core compiler AND all target emitters ... Review for: (1) IR pass correctness — ensure SSA form and type invariants are maintained."**

Also applies to: 6401-6401

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 — 0 bugs, 4 gaps

The PR unifies three associated-type constraint forms (: IFoo, where A : IFoo, __constraint) into one representation, adds the __constraint keyword for derived interfaces, replaces hardcoded operand-index access in autodiff with a role-based getInterfaceEntryByBuiltinRequirement, and adds a hoistable IRBuiltinRequirementKey deduplicated by role. Findings are all gaps: a missing module-version bump, a missing user-guide entry for the new __constraint keyword, missing test coverage for several behaviors the PR explicitly implements (subtype __constraint, syntax equivalence, enum : bool NoneWitness path, multi-segment subjects), and unguarded null-deref at every callsite of the new role-based lookup helper.

Changes Overview

Constraint unification & new __constraint syntax (slang-parser.cpp, slang-check-decl.cpp, slang-check-inheritance.cpp, slang-ast-decl-ref.cpp, slang-lower-to-ir.cpp)

  • parseAssocType routes both the : clause and the where clause to the enclosing interface; new parseInterfaceConstraintDecl adds __constraint (subtype or equality) as a sibling member of the interface. findWitnessForInterfaceRequirement validates an interface-level GenericTypeConstraintDecl requirement (with == requiring a type-equality witness). _calcInheritanceInfo walks the anchor chain of an associated-type access, matches constraints whose subject (relative to This) names the chain, and surfaces the opposite endpoint as a base — guarded against cycles (isBeingChecked()) and unbounded equality recursion (depth check). LookupDeclRef::tryResolve resolves a GenericTypeConstraintDecl requirement key to its associated-type role from the constraint's subject. EnumDecl synthesis now records the __Tag : __BuiltinIntegerType witness (using NoneWitness for bool).

Hoistable role-based requirement key (slang-ir-insts.lua, slang-ir-insts-stable-names.lua, slang-ir-insts.h, slang-ast-support-types.h, slang-lower-to-ir.cpp, slang-ir-link.cpp, slang-emit-c-like.cpp)

  • New hoistable IRBuiltinRequirementKey(kind) deduplicated by its kind operand and IRBuiltinRequirementDecoration(kind). BuiltinRequirementKind extended with DifferentialWitness, DifferentialPtrWitness, BwdCallableContextWitness. getInterfaceRequirementKey returns the hoistable key for built-in roles (including derived witness roles). Linker clones built-in-requirement entries eagerly (no mangled name); emit skips kIROp_BuiltinRequirementKey. The requirement-key type is widened from IRStructKey* to IRInst* in getInterfaceRequirementKey, interfaceRequirementKeys, addEntry, slang-ir-typeflow-specialize.cpp, and slang-ir-lower-dynamic-dispatch-insts.cpp.

Role-based autodiff requirement access (slang-ir-autodiff.cpp, slang-ir-autodiff.h, slang-ir-autodiff-fwd.cpp)

  • New getInterfaceEntryByBuiltinRequirement(interface, kind) replaces the deleted getInterfaceEntryAtIndex. AutoDiffSharedContext and maybeTranslateBackwardDerivativeWitness now address IDifferentiable / IBackwardDifferentiable / IBwdCallable requirements by role. Brittle getRequirementCount() == N asserts are removed.

Lowering of relocated subtype constraints (slang-lower-to-ir.cpp)

  • addEntry lowers a relocated GenericTypeConstraintDecl subtype constraint with a WitnessTableType requirement-val (matching the previous nested-bound shape); equality constraints take the generic path. visitGenericTypeConstraintDecl returns the requirement key when the constraint is a direct interface member.

Tests (tests/language-feature/interfaces/derived-interface-constraint*.slang)

  • Three INTERPRET / DIAGNOSTIC tests exercising single-segment __constraint X == This (basic, multi-level inheritance, and a Bad : IDerived whose DataType != This is rejected with E38029).
Findings (4 total)
Severity Location Finding
🟡 Gap source/slang/slang-ir-insts-stable-names.lua:837 (and slang-ir.h:2180) k_maxSupportedModuleVersion not bumped despite adding two new IR ops
🟡 Gap source/slang/slang-parser.cpp:10341 New __constraint keyword not documented in docs/user-guide/
🟡 Gap tests/language-feature/interfaces/derived-interface-constraint.slang Missing tests: subtype __constraint, :/where equivalence, enum : bool NoneWitness, multi-segment subjects
🟡 Gap source/slang/slang-ir-autodiff-fwd.cpp:~925 (and ~3550–3667, plus slang-ir-autodiff.cpp ctor) getInterfaceEntryByBuiltinRequirement(...)->getRequirementKey() callers do not null-check

["IncrementBranchCoverageCounter"] = 859
["IncrementBranchCoverageCounter"] = 859,
["Decoration.BuiltinRequirementDecoration"] = 860,
["builtinRequirementKey"] = 861
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: IRModule::k_maxSupportedModuleVersion not bumped despite adding two new IR ops

This PR registers two new stable-named IR ops here (Decoration.BuiltinRequirementDecoration = 860, builtinRequirementKey = 861), but source/slang/slang-ir.h:2180 still has k_maxSupportedModuleVersion = 18. The comment immediately above that constant (lines 2171-2174) is explicit about the contract:

Additionally this should be updated when new instructions are added, however only k_maxSupportedModuleVersion needs to be incremented in that case

A pre-PR Slang build that loads a module serialized by this PR will read it at the same version 18 as one with no new ops, so it has no way to refuse loading a module that may contain kIROp_BuiltinRequirementKey or kIROp_BuiltinRequirementDecoration it doesn't know how to interpret. That defeats the purpose of the version field for these two ops.

Suggested fix: bump k_maxSupportedModuleVersion from 18 to 19 in source/slang/slang-ir.h.


_makeParseDecl("typedef", parseTypeDef),
_makeParseDecl("associatedtype", parseAssocType),
_makeParseDecl("__constraint", parseInterfaceConstraintDecl),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Gap: New user-facing __constraint keyword is not documented in docs/user-guide/

This line registers __constraint as a new top-level decl keyword that users can write in interface bodies (e.g. __constraint DataType == This; or __constraint X : IFoo;). The chapter that covers associatedtype constraints and where clauses (docs/user-guide/06-interfaces-generics.md) is unchanged, so users have no way to discover the new syntax, when it differs from where clauses, or that subtype (:) and equality (==) forms are both accepted.

The PR description also says "associatedtype A : IFoo" and "associatedtype A where A : IFoo" are now genuinely the same representation — that user-visible equivalence claim deserves to be documented, since users have so far been told they may differ.

Suggested fix: add a subsection to docs/user-guide/06-interfaces-generics.md (or wherever associated-type bounds are described) documenting:

  • the __constraint <type> == <type> and __constraint <type> : <type> forms inside an interface body,
  • that __constraint X : I is equivalent to attaching : I directly to X's associatedtype decl,
  • the derived-interface use case (interface IDerived : IBase { __constraint DataType == This; }) that motivated this PR.

If __constraint is intentionally an internal/unstable spelling, leading-double-underscore aside, please mark it as such where it's parsed (or gate it behind an internal-only mode), so users don't start depending on it.

@@ -0,0 +1,46 @@
//TEST:INTERPRET(filecheck=CHECK):
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: Test coverage misses several behaviors the PR explicitly implements

The three new tests (this file, -deep, -error) all use the __constraint X == Y form on an interface that constrains DataType against This. Several behaviors the PR explicitly implements have no test:

  1. __constraint X : IFoo (subtype, not equality) is untested. parseInterfaceConstraintDecl accepts both OpEql and Colon, and slang-lower-to-ir.cpp::addEntry has a special branch for non-equality relocated subtype constraints that lowers them with a WitnessTableType requirement value (!relocatedSubtypeConstraint.getDecl()->isEqualityConstraint). That entire branch — and its consumer side in autodiff/dynamic-dispatch — is reached today only indirectly through the desugared associatedtype A : IBar form. A test like interface IDerived : IBase { __constraint DataType : ISomething; } plus a generic <T : IDerived> consumer that calls a method declared on ISomething via t.getData() would pin this path. Pair with a negative diagnostic test where T.DataType does not implement ISomething.

  2. Equivalence of : IFoo and where A : IFoo is unverified. The parser comment claims the two surface forms produce identical representations after this PR. There is no test that exercises both syntaxes for the same scenario in the same file (or in paired files) and asserts they accept/reject the same conformances. A regression that broke just one of the two parser paths would still pass every existing test.

  3. enum : bool (the NoneWitness fallback) is untested. slang-check-decl.cpp::visitEnumDecl now synthesizes a witness for __Tag : __BuiltinIntegerType; for an integer tag type it emits the real subtype witness, but for bool (a permitted tag type that does not conform to __BuiltinIntegerType) it emits NoneWitness. The PR comment notes this "addresses a long-standing TODO that became load-bearing once the bound is modeled as a requirement," meaning a regression here would silently break compilation of any usage of enum : bool. Today the only enum : bool test (tests/bugs/11043-enum-constant-wraparound.slang) is a wraparound diagnostic that does not exercise lowering. An INTERPRET test that defines enum E : bool { ... }, instantiates one, and switches on it would lock this in. Pair with enum E : int to also cover the real-witness branch.

  4. Multi-segment constraint subjects (This.TA.TB) are untested. slang-check-inheritance.cpp adds a multi-anchor walk and a "skip a constraint that is currently being checked" cycle-breaker explicitly described as needed when "resolving a multi-level subject (e.g. This.TA.TB)". derived-interface-constraint-deep.slang exercises interface-inheritance depth (IBase → IMid → ILeaf) but only a single-segment subject (DataType == This); the cycle-breaker and the deeper part of the chain-decomposition logic are never reached. A test with __constraint This.TA.TB == This (or similar two-segment subject) would cover this.

// by built-in requirement *role* rather than by operand position. Addressing by
// role is robust to the order in which requirements (and the relocated
// `BwdCallable : IBwdCallable` conformance) appear in the interface.
auto bwdDiffContextTypeReqKey = getInterfaceEntryByBuiltinRequirement(
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: Every getInterfaceEntryByBuiltinRequirement(...)->getRequirementKey() call is unguarded against nullptr

The new helper (defined in slang-ir-autodiff.cpp) returns nullptr when the requested role is not present on the interface:

IRInterfaceRequirementEntry* getInterfaceEntryByBuiltinRequirement(
    IRInterfaceType* interface,
    BuiltinRequirementKind kind)
{
    if (!interface)
        return nullptr;
    for (UInt i = 0; i < interface->getOperandCount(); i++)
    {
        ...
    }
    return nullptr;
}

Every caller in this file (~6 sites starting at this line) and every caller in maybeTranslateBackwardDerivativeWitness (8 sites in the same file at lines ~3550–3667) and in AutoDiffSharedContext (slang-ir-autodiff.cpp::AutoDiffSharedContext constructor) chains ->getRequirementKey() directly on the result. If a built-in requirement decoration is missing for any reason — a stripped/partial core module, a future refactor that forgets to mint the decoration, or a cross-module link that drops it on the deduplicated hoistable key — the chain segfaults instead of producing a diagnostic.

The previous index-based code (cast<IRInterfaceRequirementEntry>(interface->getOperand(N))) at least failed via SLANG_UNEXPECTED in getInterfaceEntryAtIndex with a string identifying the issue. Role-based lookup gives up that property silently.

Suggested fix: either add SLANG_ASSERT(entry) (or a SLANG_RELEASE_ASSERT with a message naming the missing kind) immediately after each call before the ->getRequirementKey() chain, or push the assert inside getInterfaceEntryByBuiltinRequirement itself with the requested kind in the message so any null result fails loudly with the role name attached.

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

Labels

pr: non-breaking PRs without breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants