Skip to content

Comments

Add data layout parameter to pointers#10058

Open
juliusikkala wants to merge 14 commits intoshader-slang:masterfrom
juliusikkala:pointer-data-layout-2
Open

Add data layout parameter to pointers#10058
juliusikkala wants to merge 14 commits intoshader-slang:masterfrom
juliusikkala:pointer-data-layout-2

Conversation

@juliusikkala
Copy link
Contributor

@juliusikkala juliusikkala commented Feb 17, 2026

This is the implementation of the pointer data layout proposal shader-slang/spec#39. The patch adds a new generic parameter to Ptr<T> that indicates the data layout used for storage of T when accessed through that pointer. The default behavior (scalar layout) is retained unless a layout is explicitly specified. This parameter behaves similarly to the data layout parameter on buffer types (StructuredBuffer, ConstantBuffer).

On the IR level, IRPtrType also gains a new operand that indicates the data layout type. In addition to explicitly defined layouts, this operand is also used when lowering buffer accesses into pointers via RWStructuredBufferGetElementPtr. This way, we don't lose the layout information when representing buffer data as a pointer in IR.

Not sure if this is a breaking change, but just to be cautious, I marked it as such. I think breakage in user code is very unlikely, and no existing tests were broken.

The addition of layout info to pointers also unblocks creating a lowering pass that turns "opaque" resource types into concrete struct types for LLVM on CPU (#9478), but I'll leave that to a separate PR that I'll work on some time soon. (that's the thing I'm actually excited to work on, I kinda dragged my feet on this PR because this is less fun plumbing TBH)

Meta

I briefly attempted implementing the other approach suggested by @csyonghe. That got quite ugly inside the compiler, and I repeatedly ran into very sticky situations with it, so instead of spending more time on that, I went with the original proposal.

I'm not opposed to that approach, I quite like it in fact. But it's a lot of effort. I have a feeling that the data layout infrastructure really is in need of a larger overhaul anyway, it feels pretty messy right now. Such an overhaul would be the perfect opportunity to implement that approach.

I'm open to also taking a second look at implementing that approach if there's a clear route forward with it, it's just that I don't really want to spend many weeks of spare time implementing it. I'd rather want to spend my time improving the LLVM emitter.

So if we decide that the other approach is preferred, I'd suggest that the proposal in its current form is closed. Instead, the type layout parameter can be introduced on the IR side only, with users having no way to specify an explicit layout for a pointer. That way, we wouldn't build up even more legacy syntax, but I'd be able proceed with #9478.

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

📝 Walkthrough

Walkthrough

Pointer types gain an explicit data-layout parameter that is threaded through language-level pointer aliases, AST/IR pointer construction, legalization, emission, and buffer-lowering code; new buffer layout kinds, API adjustments, and tests validate layout-specific pointer/stride behaviour.

Changes

Cohort / File(s) Summary
Core Language Definition
source/slang/core.meta.slang, source/slang/hlsl.meta.slang
Add L:IBufferDataLayout generic to Ptr and related aliases; introduce LayoutPtr; update intrinsics (__load_coherent, __store_coherent), helpers (loadCoherent, storeCoherent) and structured-buffer pointer helpers to carry L.
AST Builder & Types
source/slang/slang-ast-builder.cpp, source/slang/slang-ast-builder.h, source/slang/slang-ast-type.cpp, source/slang/slang-ast-type.h
Add Type* dataLayout parameter to getPtrType overloads; implement PtrTypeBase::getDataLayout(); thread and print the data-layout operand in pointer types.
Type Checking / Expressions
source/slang/slang-check-expr.cpp
Pass default layout when synthesizing pointer types from expressions and propagate layout through address-of and deref-derived pointer paths.
IR Core & Builder
source/slang/slang-ir.cpp, source/slang/slang-ir.h, source/slang/slang-ir-insts.*, source/slang/slang-ir-insts.lua, source/slang/slang-ir-insts-stable-names.lua
Add optional dataLayout operand to Ptr/RefParam/BorrowInParam/PseudoPtr; extend IRBuilder getPtrType overloads to accept IRType* dataLayoutType and thread it across pointer construction sites; bump supported module version.
IR Lowering / Buffer Layouts
source/slang/slang-ir-lower-buffer-element-type.cpp, source/slang/slang-ir-lower-buffer-element-type.h
Add helpers to map layout rules to layout ops/types, produce/copy layout-aware pointer types, materialize layout types for buffers, and canonicalize buffer-access pointer types; expose new functions in header.
IR Legalize / Specialize
source/slang/slang-ir-legalize-*.cpp, source/slang/slang-ir-specialize-*.cpp, source/slang/slang-ir-legalize-varying-params.cpp, source/slang/slang-ir-legalize-varying-params.h
Preserve and propagate pointer data layout during legalization and address-space specialization; CPU entry-point varying-param legalization now accepts TargetProgram* target to select target-appropriate buffer layout.
SPIR-V / GLSL / Emit
source/slang/slang-emit-spirv.cpp, source/slang/slang-ir-spirv-legalize.cpp, source/slang/slang-ir-glsl-legalize.cpp
Thread dataLayout into SPIR-V/GLSL pointer creation and legalization; propagate layout from source pointers when changing address spaces and when generating element/field pointers.
LLVM Emitter
source/slang/slang-emit-llvm.cpp
Refactor layout handling: centralize on defaultPointerRules, remove many explicit layout-rule parameters from calls, and simplify emitStackVariable signature.
Lowering to IR
source/slang/slang-lower-to-ir.cpp
When lowering Slang pointer types, lower optional DataLayout and pass it into IR getPtrType so IR pointers can carry layout.
IR Pointer Canonicalization / Fixups
source/slang/slang-ir-legalize-types.cpp, source/slang/slang-ir-specialize-address-space.cpp, source/slang/slang-ir-specialize-resources.cpp
Update pointer-copy and specialization paths to propagate existing ptr->getDataLayout() when constructing new pointer types.
Stable Names & Layout Kinds
source/slang/slang-ir-insts-stable-names.lua, source/slang/slang-ir-insts.lua
Add new layout kinds and stable-name entries for D3DConstantBufferLayout, MetalParameterBlockLayout, CUDALayout, LLVMLayout; extend PtrTypeBase operands with optional dataLayout.
Tests
tests/language-feature/pointer/data-layouts.slang
Add test exercising pointer types with different buffer layouts and verifying layout-specific offsets/strides (Std430, Scalar, C).

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Source as Source AST
participant ASTB as ASTBuilder
participant IRB as IRBuilder
participant Legalize as Legalization passes
participant Emit as Emitters (SPIR-V/LLVM)
Source->>ASTB: request pointer type (Ptr<T,..., L?>)
ASTB->>IRB: getPtrType(valueType, access, addrSpace, dataLayout)
IRB->>Legalize: create/layout-aware pointer IR (ptr with dataLayout)
Legalize->>IRB: propagate/copy dataLayout during transforms
IRB->>Emit: emit target-specific pointer representation (use dataLayout)
Emit->>Emit: select layout rules (buffer vs pointer) and decorate output

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

"I hopped through types and layouts wide,
I threaded L where pointers hide,
From AST to IR I left a trail,
So buffers stride without a fail —
🐇✨"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: adding a data layout parameter to pointer types. It accurately reflects the main technical change across the extensive modifications to the codebase.
Description check ✅ Passed The description comprehensively explains the implementation of the pointer data layout proposal, detailing changes to both the language-level Ptr type and the IR-level IRPtrType. It provides context about the proposal, mentions specific behaviors, and acknowledges design decisions.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@slangbot
Copy link
Contributor

slangbot commented Feb 17, 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.

@juliusikkala juliusikkala added the pr: breaking change PRs with breaking changes label Feb 17, 2026
Copy link
Contributor Author

@juliusikkala juliusikkala left a comment

Choose a reason for hiding this comment

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

Self-review for pointing out parts I don't personally like but don't know better ways.


/// Represents a pointer with custom data layout as a shorthand for Ptr<T>.
/// Such pointers are almost always in the Device address space.
typealias LayoutPtr<T, L:IBufferDataLayout> = Ptr<T, Access.ReadWrite, AddressSpace.Device, L>;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This differs from the proposal as it is now, will update if there's no opposition to adding this typealias.

Comment on lines +568 to +578
for (auto typeConstraintDeclRef :
containerDeclRef.getDecl()->getDirectMemberDeclsOfType<TypeConstraintDecl>())
{
if (typeConstraintDeclRef->getSup().type == dataLayoutInterface)
{
subtypeWitness = getDeclaredSubtypeWitness(
dataLayoutType,
m_sharedASTBuilder->getIBufferDataLayoutType(),
typeConstraintDeclRef);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the data layout parameter has a constraint (IBufferDataLayout), we unfortunately have to dig the subtype witness here :/

defaultPointerRules = IRTypeLayoutRules::get(IRTypeLayoutRuleName::D3DConstantBuffer);
else
defaultPointerRules = IRTypeLayoutRules::get(IRTypeLayoutRuleName::LLVM);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This logic is now instead handled via getTypeLayoutRuleNameForBuffer, which now also operates with pointer types.

Comment on lines -1558 to -1592
// Tries to find which layout rules apply to the given pointer, based on
// "provenance": we track the pointer to where we got it and check if the
// source is a buffer with a specific layout.
IRTypeLayoutRules* getPtrLayoutRules(IRInst* ptr)
{
// Check if the pointer is actually based on an buffer with an explicit
// layout. If so, we need to take that layout into account.
if (auto structuredBufferInst = as<IRRWStructuredBufferGetElementPtr>(ptr))
{
auto baseType = cast<IRHLSLStructuredBufferTypeBase>(
structuredBufferInst->getBase()->getDataType());
return getBufferLayoutRules(baseType);
}
else if (auto cbufType = as<IRConstantBufferType>(ptr->getDataType()))
{
return getBufferLayoutRules(cbufType);
}
else if (auto gep = as<IRGetElementPtr>(ptr))
{
// Transitive
return getPtrLayoutRules(gep->getBase());
}
else if (auto off = as<IRGetOffsetPtr>(ptr))
{
// Transitive
return getPtrLayoutRules(off->getBase());
}
else if (auto fieldAddr = as<IRFieldAddress>(ptr))
{
// Transitive
return getPtrLayoutRules(fieldAddr->getBase());
}
return defaultPointerRules;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good riddance, tbh. Really didn't like this code.

Comment on lines +3078 to +3080
defaultPointerRules = getTypeLayoutRuleForBuffer(
codeGenContext->getTargetProgram(),
irBuilder.getPtrType(irBuilder.getVoidType()));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not super pretty, but I really want to use the common logic from getTypeLayoutRuleForBuffer everywhere so as not to duplicate it in each target.

Comment on lines 729 to 732
["Type.D3DConstantBufferLayout"] = 727,
["Type.MetalParameterBlockLayout"] = 728,
["Type.CUDALayout"] = 729,
["Type.LLVMLayout"] = 730,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These IR insts are added because after this PR, RWStructuredBufferGetElementPtr and similar functions actually have an IR pointer type with the correct layout rule as an operand. So every layout rule needs to have a corresponding IR type now, not only the ones that are available to users.

Comment on lines +2168 to +2170
auto dataLayout = isCPUTargetViaLLVM(target->getTargetReq())
? (IRType*)builder.getLLVMBufferLayoutType()
: (IRType*)builder.getCBufferLayoutType();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is actually a bit of a drive-by fix; the LLVM target is constructing ComputeThreadVaryingInput with contained vectors aligned to 16 bytes while the C++ target aligns to 4 bytes, so this just documents that correctly to the pointer type.

Comment on lines +1649 to +1651
// Fix the pointer types of `RWStructuredBufferGetElementPtr` and
// `FieldAddress` to be layout-aware.
fixBufferAccessPointerTypes(module->getModuleInst());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't like this, as it's fixing IR after-the-fact. But there's no clean alternative: the offending instructions RWStructuredBufferGetElementPtr, FieldAddress and GetElementPtr are emitted before the IR is specialized to any target, and as such, we cannot know which concrete layout type is being used by a buffer until running target-specific passes. Some parts of that layout selection is target-dependent and DefaultDataLayout means different things depending on buffer type.

Comment on lines +2382 to +2391
if (auto ptrType = as<IRPtrTypeBase>(bufferType))
{
auto layoutTypeOp = ptrType->getDataLayout() ? ptrType->getDataLayout()->getOp()
: kIROp_DefaultBufferLayoutType;

IRTypeLayoutRuleName defaultRule = IRTypeLayoutRuleName::Natural;
if (isCPUTargetViaLLVM(targetReq))
defaultRule = IRTypeLayoutRuleName::LLVM;

return getTypeLayoutRulesFromOp(layoutTypeOp, defaultRule);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function also now handles the layout of pointer types, as they're very similar.

@juliusikkala juliusikkala marked this pull request as ready for review February 18, 2026 23:28
@juliusikkala juliusikkala requested a review from a team as a code owner February 18, 2026 23:28
Copy link

@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

Caution

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

⚠️ Outside diff range comments (3)
source/slang/slang-emit-llvm.cpp (1)

1707-1725: ⚠️ Potential issue | 🟡 Minor

IRTypeLayoutRules::get() can return null in the default case; verify all enum values are handled.

The code correctly handles pointer types via getTypeLayoutRuleNameForBuffer, which explicitly checks IRPtrTypeBase at line 2382 and respects the dataLayout operand with fallback to kIROp_DefaultBufferLayoutType. For the specific initialization of defaultPointerRules via Ptr<void>, the pointer path returns Natural, which is never null.

However, IRTypeLayoutRules::get() (line 907-930) has a default case returning nullptr. While all current code paths should avoid this (through proper enum handling), this design creates a latent vulnerability: if a new IRTypeLayoutRuleName enum value is added without updating the switch statement, or if getTypeLayoutRulesFromOp maps to an unmapped enum value, defaultPointerRules could become null without detection. Consider asserting that the result is non-null, or ensuring all enum variants are exhaustively handled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/slang/slang-emit-llvm.cpp` around lines 1707 - 1725, The call to
IRTypeLayoutRules::get() can return nullptr in its default switch branch; update
the code paths that assume a non-null result (notably the initialization of
defaultPointerRules and uses via
getTypeLayoutRuleNameForBuffer/getTypeLayoutRulesFromOp) to validate the
returned pointer immediately—either assert/abort on nullptr or throw a clear
error—so any unmapped IRTypeLayoutRuleName enum value is caught early;
specifically, after calling IRTypeLayoutRules::get(...) check the result and
fail fast with a descriptive message referencing the rule name (or add
exhaustive handling in IRTypeLayoutRules::get to never return nullptr).
source/slang/slang-ast-builder.cpp (1)

556-583: ⚠️ Potential issue | 🟠 Major

Guard against invalid/null dataLayoutType to prevent null pointer dereference crashes.

The cast chain as<DeclRefType>(dataLayoutType)->getDeclRef() dereferences immediately without validating the cast succeeded. If dataLayoutType is null or fails the type check, this will crash. Error recovery paths during compilation may produce invalid types; a defensive fallback using the existing getDefaultLayoutType() method ensures robustness:

Proposed hardening
 PtrTypeBase* ASTBuilder::getPtrType(
     Type* valueType,
     Val* accessQualifier,
     Val* addrSpace,
     Type* dataLayoutType,
     char const* ptrTypeName)
 {
-    auto declRef = as<DeclRefType>(dataLayoutType)->getDeclRef();
-    auto containerDeclRef = declRef.as<ContainerDecl>();
+    if (!dataLayoutType || !as<DeclRefType>(dataLayoutType))
+        dataLayoutType = getDefaultLayoutType();
+    auto declRefType = as<DeclRefType>(dataLayoutType);
+    SLANG_RELEASE_ASSERT(declRefType);
+    auto declRef = declRefType->getDeclRef();
+    auto containerDeclRef = declRef.as<ContainerDecl>();
+    SLANG_RELEASE_ASSERT(containerDeclRef.getDecl());
     auto dataLayoutInterface = m_sharedASTBuilder->getIBufferDataLayoutType();
     DeclaredSubtypeWitness* subtypeWitness = nullptr;

-    for (auto typeConstraintDeclRef :
-         containerDeclRef.getDecl()->getDirectMemberDeclsOfType<TypeConstraintDecl>())
+    auto containerDecl = containerDeclRef.getDecl();
+    for (auto typeConstraintDeclRef :
+         containerDecl->getDirectMemberDeclsOfType<TypeConstraintDecl>())
     {
         if (typeConstraintDeclRef->getSup().type == dataLayoutInterface)
         {
             subtypeWitness = getDeclaredSubtypeWitness(
                 dataLayoutType,
                 m_sharedASTBuilder->getIBufferDataLayoutType(),
                 typeConstraintDeclRef);
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/slang/slang-ast-builder.cpp` around lines 556 - 583, In
ASTBuilder::getPtrType, defensively validate dataLayoutType before doing
as<DeclRefType>(dataLayoutType)->getDeclRef(): if dataLayoutType is null or not
a DeclRefType, replace it with m_sharedASTBuilder->getDefaultLayoutType() and
then proceed; use the (possibly replaced) dataLayoutType when obtaining declRef,
containerDeclRef, and when calling getDeclaredSubtypeWitness(...,
m_sharedASTBuilder->getIBufferDataLayoutType(), ...); preserve the existing
SLANG_RELEASE_ASSERT(subtypeWitness) and pass the resulting subtypeWitness into
getSpecializedBuiltinType as before so you avoid null-dereference crashes while
keeping current behavior.
source/slang/slang-ir-legalize-varying-params.cpp (1)

2158-2175: ⚠️ Potential issue | 🟠 Major

Propagate dataLayout to field pointer types for layout consistency.

uint3PtrType is created before the dataLayout decision (line 2160), but varyingInputStructPtrType is created with target-specific layout (lines 2168–2175). When emitFieldAddress(uint3PtrType, varyingInputParam, ...) is called on lines 2226 and 2229, the field pointers lack the struct's layout information, breaking address/offset calculations under LLVM vs C layouts.

Proposed fix
-        uint3PtrType = builder.getPtrType(uint3Type);
-
         // As we construct the `ComputeThreadVaryingInput` type and its fields,
         // we mark them all as target intrinsics, which means that their
         // declarations will *not* be reproduced in the output code, instead
         // coming from the "prelude" file that already defines this type.
 
         varyingInputStructType = builder.createStructType();
         auto dataLayout = isCPUTargetViaLLVM(target->getTargetReq())
                               ? (IRType*)builder.getLLVMBufferLayoutType()
                               : (IRType*)builder.getCBufferLayoutType();
+        uint3PtrType = builder.getPtrType(
+            uint3Type,
+            AccessQualifier::Read,
+            AddressSpace::Generic,
+            dataLayout);
         varyingInputStructPtrType = builder.getPtrType(
             varyingInputStructType,
             AccessQualifier::Read,
             AddressSpace::Generic,
             dataLayout);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/slang/slang-ir-legalize-varying-params.cpp` around lines 2158 - 2175,
The uint3PtrType is created before dataLayout is chosen so field pointers lack
the struct's layout and produce incorrect addresses when
emitFieldAddress(uint3PtrType, ...) is used; fix by creating the uint3PtrType
(via builder.getPtrType on the uint3 vector type) using the same dataLayout as
varyingInputStructPtrType (i.e., move or recreate the uint3PtrType construction
after dataLayout is computed or rebuild it with dataLayout) so both the struct
pointer type and the field pointer types share the same layout before calls to
emitFieldAddress.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@source/slang/core.meta.slang`:
- Around line 1364-1366: The constructor __init<U, Access accessOther,
L:IBufferDataLayout>(Ptr<U, accessOther, addrSpace, L> ptr) shadows the
struct-level generic L which permits bit-casting between pointers with different
data layouts and can produce incorrect offsets at dereference; update the code
by either renaming the constructor generic (e.g., LOther) to avoid shadowing or
add a short comment beside __init/Ptr clarifying that callers are responsible
for ensuring layout compatibility when converting between layouts (mentioning
Std140 vs Scalar as an example) so the potential layout-mismatch risk is
explicit.

In `@source/slang/slang-emit-spirv.cpp`:
- Around line 7738-7746: The code currently only sets dataLayout from
destPtrType when destPtrType->hasAddressSpace() is true, causing implicit
(Generic) address-space pointers with an explicit layout to be ignored; change
the logic in the block that builds ptrElementType so that address space still
comes from destPtrType only if hasAddressSpace() is true, but always attempt to
read dataLayout from destPtrType (e.g. call destPtrType->getDataLayout() and use
it if non-null/valid); update the code around AddressSpace::Function,
builder.getDefaultBufferLayoutType(), destPtrType->hasAddressSpace(),
destPtrType->getDataLayout(), and builder.getPtrType(...) to propagate layout
independently of address-space presence.

In `@source/slang/slang-ir-glsl-legalize.cpp`:
- Around line 1760-1765: The derived field pointer types in extractField and
getSubscriptVal are losing pointer attributes because they call
getPtrType(ptrType->getOp(), fieldType) / getPtrType(elementType) without
propagating the parent pointer's access qualifier, address space, and data
layout; update both sites to use getPtrTypeWithAddressSpace(fieldType, ptrType)
(or otherwise call getPtrType with ptrType->getAccessQualifier(),
ptrType->getAddressSpace(), ptrType->getDataLayout()) so the new pointer
preserves ptrType's attributes and maintains IR type invariants.

In `@source/slang/slang-ir-lower-buffer-element-type.cpp`:
- Around line 2057-2065: getPointerTypeWithBufferLayout currently hardcodes
AccessQualifier::ReadWrite and AddressSpace::Generic; change it to mirror
copyBufferLayoutToPointer by extracting the pointer qualifiers from bufferType
(cast to IRPtrType or use its accessor) and passing those AccessQualifier and
AddressSpace values into builder.getPtrType alongside elementType and layoutType
(use getBufferTypeLayoutType(builder, bufferType) for layout). Ensure you handle
the case where bufferType is not a pointer consistently with
copyBufferLayoutToPointer.

In `@source/slang/slang-ir-spirv-legalize.cpp`:
- Around line 238-243: The synthesized pointer created via
builder.getPtrType(...) inside builder.emitFieldAddress currently uses
builder.getDefaultBufferLayoutType(), which discards any explicit dataLayout
from the original buffer; change the code that constructs the new pointer (the
call to builder.getPtrType in the emitFieldAddress site and the similar sites at
the other locations) to retrieve and pass the originating pointer's dataLayout
(e.g. use oldPtrType->getDataLayout() or an extracted dataLayout value when
available) instead of getDefaultBufferLayoutType(), so the new ptrType preserves
the original buffer layout and ptrType->getDataLayout() remains non-null for
downstream passes (see usages of oldPtrType->getDataLayout() and
ptrType->getDataLayout() elsewhere).

In `@tests/language-feature/pointer/data-layouts.slang`:
- Around line 1-2: Replace the GPU-backed compute directives that use
`-llvm`/`-vk` and `-compute` (the lines starting with
`//TEST(compute):COMPARE_COMPUTE_EX(...)`) with CPU/interpreter compute
directives so the test is GPU-free; specifically, change those directives to use
the CPU compute driver and output-using-type form (for example use the pattern
`//TEST:COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -output-using-type`) so the
test runs on the CPU/interpreter rather than LLVM/VK GPU paths.

---

Outside diff comments:
In `@source/slang/slang-ast-builder.cpp`:
- Around line 556-583: In ASTBuilder::getPtrType, defensively validate
dataLayoutType before doing as<DeclRefType>(dataLayoutType)->getDeclRef(): if
dataLayoutType is null or not a DeclRefType, replace it with
m_sharedASTBuilder->getDefaultLayoutType() and then proceed; use the (possibly
replaced) dataLayoutType when obtaining declRef, containerDeclRef, and when
calling getDeclaredSubtypeWitness(...,
m_sharedASTBuilder->getIBufferDataLayoutType(), ...); preserve the existing
SLANG_RELEASE_ASSERT(subtypeWitness) and pass the resulting subtypeWitness into
getSpecializedBuiltinType as before so you avoid null-dereference crashes while
keeping current behavior.

In `@source/slang/slang-emit-llvm.cpp`:
- Around line 1707-1725: The call to IRTypeLayoutRules::get() can return nullptr
in its default switch branch; update the code paths that assume a non-null
result (notably the initialization of defaultPointerRules and uses via
getTypeLayoutRuleNameForBuffer/getTypeLayoutRulesFromOp) to validate the
returned pointer immediately—either assert/abort on nullptr or throw a clear
error—so any unmapped IRTypeLayoutRuleName enum value is caught early;
specifically, after calling IRTypeLayoutRules::get(...) check the result and
fail fast with a descriptive message referencing the rule name (or add
exhaustive handling in IRTypeLayoutRules::get to never return nullptr).

In `@source/slang/slang-ir-legalize-varying-params.cpp`:
- Around line 2158-2175: The uint3PtrType is created before dataLayout is chosen
so field pointers lack the struct's layout and produce incorrect addresses when
emitFieldAddress(uint3PtrType, ...) is used; fix by creating the uint3PtrType
(via builder.getPtrType on the uint3 vector type) using the same dataLayout as
varyingInputStructPtrType (i.e., move or recreate the uint3PtrType construction
after dataLayout is computed or rebuild it with dataLayout) so both the struct
pointer type and the field pointer types share the same layout before calls to
emitFieldAddress.

@szihs szihs requested a review from csyonghe February 19, 2026 10:48
Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
source/slang/slang-ir-spirv-legalize.cpp (1)

447-532: ⚠️ Potential issue | 🟠 Major

Apply buffer-specific layout derivation instead of relying on generic default fallback.

When getDataLayout() returns null for uniform parameter groups, structured buffers, or GLSL shader storage buffers, getPtrType() falls back to getDefaultBufferLayoutType(). However, the correct layout should be derived per-buffer-type using getTypeLayoutTypeForBuffer(), which accounts for target-specific layout rules. This ensures proper data packing, matching the pattern already used at line 243 for constant buffer parameters.

🔧 Fix: Use derived layout instead of generic fallback
-                dataLayout = uniformParamGroupType->getDataLayout();
+                if (auto layout = uniformParamGroupType->getDataLayout())
+                    dataLayout = layout;
+                else
+                    dataLayout = getTypeLayoutTypeForBuffer(
+                        m_sharedContext->m_targetProgram,
+                        builder,
+                        uniformParamGroupType);
...
-                dataLayout = structuredBufferType->getDataLayout();
+                if (auto layout = structuredBufferType->getDataLayout())
+                    dataLayout = layout;
+                else
+                    dataLayout = getTypeLayoutTypeForBuffer(
+                        m_sharedContext->m_targetProgram,
+                        builder,
+                        structuredBufferType);
...
-                dataLayout = glslShaderStorageBufferType->getDataLayout();
+                if (auto layout = glslShaderStorageBufferType->getDataLayout())
+                    dataLayout = layout;
+                else
+                    dataLayout = getTypeLayoutTypeForBuffer(
+                        m_sharedContext->m_targetProgram,
+                        builder,
+                        glslShaderStorageBufferType);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/slang/slang-ir-spirv-legalize.cpp` around lines 447 - 532, The code
currently uses builder.getDefaultBufferLayoutType() as a fallback when
innerType->getDataLayout() is null (e.g., in the IRUniformParameterGroupType,
IRHLSLStructuredBufferTypeBase/derived, and IRGLSLShaderStorageBufferType
branches), which is wrong; instead call the buffer-specific layout derivation
helper (getTypeLayoutTypeForBuffer) to compute the correct layout. Update the
branches that set dataLayout (where code currently does dataLayout =
uniformParamGroupType->getDataLayout(), dataLayout =
structuredBufferType->getDataLayout(), and dataLayout =
glslShaderStorageBufferType->getDataLayout()) so that if getDataLayout() returns
null you call getTypeLayoutTypeForBuffer(innerType) (or the corresponding
builder helper used in this codebase) and assign that to dataLayout before any
further use (this will ensure getPtrType()/pointer creation uses the proper
buffer-specific layout rather than getDefaultBufferLayoutType()). Ensure you
reference IRUniformParameterGroupType,
lowerStructuredBufferType/IRHLSLStructuredBufferTypeBase, and
IRGLSLShaderStorageBufferType when making the replacement so the change is
applied in each of those branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@source/slang/slang-ir-lower-buffer-element-type.cpp`:
- Around line 2054-2059: The formatting of the block that calls
getTypeLoweringConfigForBuffer, getTypeLayoutTypeForBuffer and
builder.getPtrType does not conform to the project's clang-format rules; re-run
the project's formatter (clang-format v17-18) or run ./extras/formatting.sh on
source/slang/slang-ir-lower-buffer-element-type.cpp to apply the required C++
formatting and commit the updated file so CI's clang-format check passes.

---

Outside diff comments:
In `@source/slang/slang-ir-spirv-legalize.cpp`:
- Around line 447-532: The code currently uses
builder.getDefaultBufferLayoutType() as a fallback when
innerType->getDataLayout() is null (e.g., in the IRUniformParameterGroupType,
IRHLSLStructuredBufferTypeBase/derived, and IRGLSLShaderStorageBufferType
branches), which is wrong; instead call the buffer-specific layout derivation
helper (getTypeLayoutTypeForBuffer) to compute the correct layout. Update the
branches that set dataLayout (where code currently does dataLayout =
uniformParamGroupType->getDataLayout(), dataLayout =
structuredBufferType->getDataLayout(), and dataLayout =
glslShaderStorageBufferType->getDataLayout()) so that if getDataLayout() returns
null you call getTypeLayoutTypeForBuffer(innerType) (or the corresponding
builder helper used in this codebase) and assign that to dataLayout before any
further use (this will ensure getPtrType()/pointer creation uses the proper
buffer-specific layout rather than getDefaultBufferLayoutType()). Ensure you
reference IRUniformParameterGroupType,
lowerStructuredBufferType/IRHLSLStructuredBufferTypeBase, and
IRGLSLShaderStorageBufferType when making the replacement so the change is
applied in each of those branches.

---

Duplicate comments:
In `@source/slang/slang-ir-glsl-legalize.cpp`:
- Around line 2597-2599: getSubscriptVal currently builds the element pointer
with builder->getPtrType(elementType) which loses pointer attributes; update the
ScalarizedVal::address branch in getSubscriptVal so it derives the element
pointer type from the parent pointer type of val.irValue (propagating access
qualifier, address space and data layout) and then call
builder->emitElementAddress with that propagated pointer type (use the existing
val.irValue pointer type's attribute accessors instead of the default getPtrType
overload).

In `@source/slang/slang-ir-lower-buffer-element-type.cpp`:
- Around line 2050-2059: getPointerTypeWithBufferLayout currently hardcodes
AccessQualifier::ReadWrite and uses loweringConfig.addressSpace directly which
can widen or drop qualifiers when bufferType is already a pointer; change it to
mirror copyBufferLayoutToPointer by extracting the pointer qualifiers and
address space from bufferType (when bufferType is a pointer) and pass those into
builder.getPtrType instead of unconditionally using ReadWrite/generic; use
getTypeLoweringConfigForBuffer and getTypeLayoutTypeForBuffer as needed, but
preserve bufferType's existing access qualifier and addressSpace when creating
the new pointer for elementType so qualifiers are not widened or lost.

Copy link

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@source/slang/slang-ir-lower-buffer-element-type.cpp`:
- Around line 2050-2062: getPointerTypeWithBufferLayout currently hardcodes
AccessQualifier::ReadWrite, which can widen read-only buffers; change it to
derive the qualifier from the incoming bufferType (or its pointer
representation) instead of using AccessQualifier::ReadWrite. In the body of
getPointerTypeWithBufferLayout, obtain the correct access qualifier by querying
bufferType (or its pointer type) via the existing helper/API used elsewhere
(e.g. getAccessQualifier or the bufferType's pointer-like accessor) and pass
that qualifier into builder.getPtrType along with the existing
loweringConfig.addressSpace and layoutType (keeping
getTypeLoweringConfigForBuffer and getTypeLayoutTypeForBuffer calls intact).
Ensure you handle both raw pointer bufferType and structured-buffer wrapper
types the same way so read-only qualifiers are preserved.

Copy link

@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-ir-insts.lua (1)

239-283: ⚠️ Potential issue | 🟠 Major

Update k_maxSupportedModuleVersion in source/slang/slang-ir.h for the new IR schema changes.

The file header in slang-ir-insts.lua explicitly states to update module versions when modifying this file. Adding the dataLayout operand to Ptr, RefParam, BorrowInParam, and PseudoPtr types (lines 245, 256, 267, 282) plus the four new layout types (lines 343-346) constitutes a schema change requiring version bump. Per the documentation in slang-ir.h (lines 2128-2130), only k_maxSupportedModuleVersion needs to be incremented for new instructions/operands; k_minSupportedModuleVersion remains unchanged.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/slang/slang-ir-insts.lua` around lines 239 - 283, You added a
schema-changing IR operand (the "dataLayout" operand) to Ptr, RefParam,
BorrowInParam, and PseudoPtr and introduced four new layout types in
slang-ir-insts.lua, so increment the module version constant: open
source/slang/slang-ir.h and bump k_maxSupportedModuleVersion (leave
k_minSupportedModuleVersion unchanged) to the next integer value so the new
instructions/operands are recognized as a newer supported module version.
🧹 Nitpick comments (1)
source/slang/slang-ir-insts.h (1)

3353-3363: Update comment to reflect that data layout is also copied.

The comment lists the properties being copied but omits the newly-added dataLayout. Consider updating for accuracy.

📝 Suggested comment update
-    // Copies the op-type of the oldPtrType, access-qualifier and address-space.
+    // Copies the op-type of the oldPtrType, access-qualifier, address-space, and data-layout.
     // Does not reuse the same `inst` for access-qualifier and address-space.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/slang/slang-ir-insts.h` around lines 3353 - 3363, The comment above
getPtrType(IRType*, IRPtrTypeBase*) incorrectly omits that data layout is also
copied from oldPtrType; update the comment to list that the op-type,
access-qualifier, address-space, and dataLayout are copied (and clarify the note
about not reusing the same `inst` for access-qualifier and address-space) so it
accurately reflects the parameters passed to the overloaded getPtrType call
using oldPtrType->getOp(), getAccessQualifier(), getAddressSpace(), and
getDataLayout().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@source/slang/slang-ir-insts.lua`:
- Around line 239-283: You added a schema-changing IR operand (the "dataLayout"
operand) to Ptr, RefParam, BorrowInParam, and PseudoPtr and introduced four new
layout types in slang-ir-insts.lua, so increment the module version constant:
open source/slang/slang-ir.h and bump k_maxSupportedModuleVersion (leave
k_minSupportedModuleVersion unchanged) to the next integer value so the new
instructions/operands are recognized as a newer supported module version.

---

Duplicate comments:
In `@source/slang/slang-ir-lower-buffer-element-type.cpp`:
- Around line 2050-2062: getPointerTypeWithBufferLayout currently hardcodes
AccessQualifier::ReadWrite; change it to derive the qualifier from the buffer
type and pass that into builder.getPtrType. Inspect the IRType/IRInst opcode of
bufferType (e.g., kIROp_HLSLStructuredBufferType -> AccessQualifier::ReadOnly,
kIROp_HLSLRWStructuredBufferType -> AccessQualifier::ReadWrite) and use that
mapping (falling back to ReadWrite if unknown) instead of the hardcoded value;
then call builder.getPtrType(elementType, derivedQualifier,
loweringConfig.addressSpace, layoutType). Ensure the change is applied inside
getPointerTypeWithBufferLayout so all call sites get the correct qualifier.

In `@source/slang/slang-ir-spirv-legalize.cpp`:
- Around line 1147-1177: Both processStructuredBufferLoad and
processRWStructuredBufferStore create a pointer type for
kIROp_RWStructuredBufferGetElementPtr without propagating the source
StructuredBuffer's dataLayout, which can drop explicit Std140/Std430 layout
info; fix by extracting the dataLayout from the structured buffer type (e.g.,
via the StructuredBuffer IR type or
sb->getFullType()->getDataLayout()/getDataLayout()) and pass that layout into
builder.getPtrType when synthesizing the pointer type for the element, so the
new pointer created for the intrinsic preserves the original buffer's
dataLayout.

---

Nitpick comments:
In `@source/slang/slang-ir-insts.h`:
- Around line 3353-3363: The comment above getPtrType(IRType*, IRPtrTypeBase*)
incorrectly omits that data layout is also copied from oldPtrType; update the
comment to list that the op-type, access-qualifier, address-space, and
dataLayout are copied (and clarify the note about not reusing the same `inst`
for access-qualifier and address-space) so it accurately reflects the parameters
passed to the overloaded getPtrType call using oldPtrType->getOp(),
getAccessQualifier(), getAddressSpace(), and getDataLayout().

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

Labels

pr: breaking change PRs with breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants