ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Slang 指针类型(Pointer Types)全面指南:声明语法、泛型指针、指针运算与并发测试验证

Slang 指针类型(Pointer Types)全面指南:声明语法、泛型指针、指针运算与并发测试验证 Slang 指针类型Pointer Types全面指南声明语法、泛型指针、指针运算与并发测试验证【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang导读本文以 Slang 语言参考文档 types-pointer.md 为骨架系统讲解 Slang 中指针类型的声明语法T*、标准库泛型指针PtrT/ImmutablePtrT、地址取址与解引用、指针算术、指针 traits访问模式与地址空间以及当前的实现限制。文中同时引入 docs/generated/tests/conformance/types-pointer 下的一组一致性测试conformance tests以真实的.slang测试用例印证文档中的每一条声明。读者读完本文后将能准确写出可编译、可运行的 Slang 指针代码理解指针在 CPU/CUDA 目标上的行为边界并能够看懂仓库中的指针相关测试是如何组织与断言CHECK的。注意Slang 文档明确标注指针在slangc中尚未完全实现Pointers are not yet fully implemented inslangc因此本文所述内容以当前仓库文档与测试所确认的范围为准。声明语法Declaration SyntaxT*语法形式与类型语义指针类型通过在类型说明符后附加*声明。语言参考中给出的类型说明符语法为simple-type-id-spec [modifier-list] type-identifier [generic-params-decl] ( [ [constant-index-expr] ] | * )*其中modifier-list可选的修饰符列表type-identifier命名现有类型或泛型类型的标识符可以是基础类型、向量/矩阵泛型类型、用户自定义的结构体类型、接口类型、枚举类型、类型别名或模块提供的类型generic-params-decl泛型参数声明[ constant-index-expr ]数组维度声明*指针声明即本主题。语义要点指针类型T*表示指向类型T的对象的地址。基类型可以是任何 addressable可寻址类型包括指针类型与数组类型即支持指向指针的指针、指向数组的指针等嵌套形式。Remark 1等价关系声明语法写出的指针类型等价于泛型指针类型PtrT, Access.ReadWrite, AddressSpace.Device。Remark 2指针也可以通过变量声明来声明——此时变量本身被声明为指针而非类型本身是指针类型。测试验证T*声明、取址与解引用往返一致性测试 declare-star-syntax-functional.slang 以INTERPRET解释执行模式验证声明、取址、解引用三者协同工作//TEST:INTERPRET(filecheckCHECK): void main() { int x 42; int* p x; //CHECK: 42 printf(%d\n, *p); }测试通过filecheckCHECK断言*p的输出为42证明int* p x完整覆盖了声明指针类型 → 取址 → 解引用的往返链路。T*与PtrT, Access.ReadWrite, AddressSpace.Device类型等价测试 ptr-star-equals-ptr-generic-functional.slang 进一步验证 Remark 1 的类型等价性——T*值可以直接赋值给全限定形式的Ptr变量无需任何转换//TEST:INTERPRET(filecheckCHECK): void main() { int x 42; int* p1 x; Ptrint, Access.ReadWrite, AddressSpace.Device p2 p1; //CHECK: 42 42 printf(%d %d\n, *p1, *p2); }两个指针解引用得到相同结果印证两种拼写是同一类型。泛型指针类型Generic Pointer TypesSlang 标准库提供两种泛型指针类型别名别名语义参数PtrT, AccessMode, AddressSpace通用指针类型T元素类型AccessMode存储访问模式AddressSpace存储地址空间ImmutablePtrT, AddressSpace指向不可变数据的指针类型别名T元素类型AddressSpace存储地址空间关于AccessMode与AddressSpace的取值详见下文 指针 traits 一节。PtrT的声明与解引用测试 ptr-generic-type-functional.slang 验证PtrT别名可声明、可从obj赋值并可解引用//TEST:INTERPRET(filecheckCHECK): void main() { int x 55; Ptrint p x; //CHECK: 55 printf(%d\n, *p); }ImmutablePtrT只读指针测试 immutableptr-type-functional.slang 验证只读别名由于没有隐式转换见 指针 traits需要通过显式 cast 从T*得到ImmutablePtrint随后可以正常解引用读取//TEST:INTERPRET(filecheckCHECK): void main() { int x 33; ImmutablePtrint ro (ImmutablePtrint)(x); //CHECK: 33 printf(%d\n, *ro); }描述Description取址、解引用与指针算术地址获取与__getAddress取址运算符用于获取对象的地址可以赋给类型匹配的指针变量标准库内建函数__getAddress(obj)是的替代写法。测试 address-of-operator-functional.slang 验证a产生可用指针、解引用还原原值*(obj) obj。测试 getaddress-builtin-functional.slang 验证__getAddress(obj)产生同样的有效指针。解引用读与写解引用运算符*访问读取被指向的对象并且*p是l-value——通过*p v可以改变被指向对象的值。测试 dereference-write-functional.slang 验证写操作对原变量可见//TEST:INTERPRET(filecheckCHECK): void main() { int x 10; int* p x; *p 99; //CHECK: 99 printf(%d\n, x); }指针作为函数参数传入时被调函数可通过解引用修改调用方变量。测试 pointer-pass-to-function-functional.slang 验证了这一点void addDelta(int* p, int delta) { *p *p delta; } void main() { int x 10; addDelta(x, 5); //CHECK: 15 printf(%d\n, x); }成员访问p-member当指针指向结构体或类类型时可以使用成员访问运算符.或-解引用并访问成员。测试 struct-arrow-member-access-functional.slang 验证p-member与(*p).member访问同一成员且通过-的修改对原对象可见C10。指针算术、-、、--当指针指向数组元素时可以对指针加/减整数值得到的指针指向偏移后的元素p nC11向前移动n个元素。测试 pointer-arithmetic-add-functional.slang 验证解引用结果正确pointer-arithmetic-spirv-emission.slang 验证发射emission形态见下文。p - nC12向后移动n个元素。测试 pointer-arithmetic-sub-functional.slang 验证。p/--pC13前进/后退一个元素。测试 pointer-increment-functional.slang 验证。各后端指针算术的发射形态pointer-arithmetic-spirv-emission.slang 使用运行时偏移从 buffer 读取偏移量以防止常量折叠验证各后端的 lowering 结果//TEST:SIMPLE(filecheckCHECK):-target spirv-asm -entry main -stage compute //TEST:SIMPLE(filecheckCHECKCPP):-target cpp -entry main -stage compute //TEST:SIMPLE(filecheckCHECKCUDA):-target cuda -entry main -stage compute [numthreads(1,1,1)] void main(RWStructuredBufferint inputBuf, RWStructuredBufferint outputBuf) { int arr[4]; arr[0] inputBuf[0]; arr[1] inputBuf[1]; arr[2] inputBuf[2]; arr[3] inputBuf[3]; int* p arr[0]; p p inputBuf[4]; // runtime offset — forces OpPtrAccessChain outputBuf[0] *p; } // CHECK: OpPtrAccessChain // CHECKCPP: arr_0[int(0)] // CHECKCUDA: arr_0[int(0)] 关键断言SPIR-V本地数组上的运行时指针算术发射为OpPtrAccessChainCPP / CUDA发射为显式的指针加法表达式arr_0[int(0)] ...。空指针与末尾之后哨兵空指针C14nullptr是合法的空指针值p nullptr对空指针为真、对非空指针为假。测试 null-pointer-functional.slang 验证。末尾之后哨兵C15arr[N-1] 1是合法的数组最后一个元素之后的指针可作为哨兵值参与比较但不可解引用。语言参考还给出了一段指针算术合法性示例Remark 2var arr : uint[10] { }; var ptr : uint *; ptr arr[9]; // OK: ptr points to the last element // of the array ptr; // Still OK: ptr points to one past the // last element ptr; // Pointer is now invalid ptr--; // No validity guarantees with invalid // pointers in pointer expressions; // dereferencing would be undefined behavior指针值的四类归属与未定义行为文档将指针值划分为四类指向具有匹配 traits 的对象的指针包括指向数组元素的指针指向对象末尾之后的指针空指针指向任何对象的特殊指针值无效指针其余情况。解引用不指向具有匹配 traits 的对象的指针是未定义行为undefined behavior。另外文档特别警告当指针指向多维数组中的元素时指针算术必须始终停留在同一最内层数组中或得到指向该数组最后一个对象之后的指针该指针不可解引用其他任何结果都是未定义行为。指针 TraitsPointer Traits指针类型具有以下 traits被指向对象的类型访问模式access mode地址空间address space。一个有效指针只能指向 traits 匹配的对象。默认值Device 地址空间 ReadWrite 访问模式默认指针地址空间是AddressSpace.Device默认访问模式是Access.ReadWrite使用声明语法无法指定其他地址空间或访问模式。测试 pointer-default-access-mode-functional.slang 验证了这一默认行为C16。其他地址空间与访问模式的指针需通过标准库类型别名PtrT, AccessMode, AddressSpace声明。禁止隐式转换读写 → 只读不存在从读写指针PtrT到只读指针ImmutablePtrT的隐式转换。测试 no-implicit-rw-to-immutable-negative.slang 以负向诊断测试DIAGNOSTIC_TEST断言编译器以错误码E30019拒绝隐式赋值//DIAGNOSTIC_TEST:SIMPLE(diagCHECK,non-exhaustive):-target cpp void main() { int x 10; Ptrint rw x; ImmutablePtrint ro rw; //CHECK: E30019 }如需只读指针必须使用显式 cast参见上文ImmutablePtrT一节。当前限制与注意点文档明确列出了指针尚未完全实现的范围一致性测试也对此进行了验证与记录局部内存指针仅支持 CPU 与 CUDA 目标。operator在核心模块中被门控在cpp_cuda_spirv_llvm能力集上对 Vulkan/D3D12 等 GPU 目标编译包含本地内存取址的着色器会以error[E36107]: unavailable features in entry point中止需 GPU 驱动环境才能验证CI 会覆盖。不支持指向不透明句柄类型如Texture2D的指针句柄指针应使用DescriptorHandleT。测试中发现一个文档缺口doc gap对全局作用域的Texture2D取址实际报错为 E30079cannot take address of immutable object而不是专门的不支持不透明句柄指针诊断——原因是资源类型是隐式不可变对象。详见一致性测试 README 的 Doc gaps observed 一节。不支持const指针。const T*声明被编译器以错误码E20017拒绝负向测试 const-pointer-rejected-negative.slang 固定了该诊断文档建议改用PtrT, Access.Read或let/const Ptr...表达只读语义。不支持自定义对齐规格对于已知对齐的指针加载/存储应使用标准库函数 loadAligned() /storeAligned()这些函数定义在本文档之外的标准库中。并非所有目标都支持指针。发射测试 declare-star-syntax-emission.slang 展示了各后端的实际表现CPP发射int32_t等原生指针类型CHECK: int32_tCUDA发射__global__内核标记SPIR-V asm发射OpTypePointer StorageBuffer存储缓冲指针类型Metal设备内存指针发射为int device* devPtrGLSL指针以GL_EXT_buffer_reference块形式渲染layout(buffer_reference, std430, buffer_reference_align 4) buffer BufferPointer_...HLSL / WGSL被排除在发射 fan-out 之外D3D 没有指针类型slangc会把 Slang 内部的Ptrint, ...泛型泄漏到 HLSL 输出中WGSL 的ptr要求地址空间而当前输出中地址空间为空。这两项分别记录为待发现项findings_meta/findings/hlsl-pointer-param-emits-internal-ptr-generic.yaml与_meta/findings/wgsl-pointer-param-emits-empty-address-space.yaml。不支持指针继承。指向符合接口I的结构体的指针不能 cast 为指向I的指针。测试记录了一个文档缺口编译器在 parse/check 阶段接受(IFoo*)bp这种显式 cast失败只会在 code-gen 阶段尝试接口动态派发时以E50100出现——即被拒绝的是派发而非 cast 本身。实战示例用指针表示半开区间[start, end)语言参考的 Examples 一节演示了指针最典型的实战用法——用一对指针表示半开区间并求和。以下完整示例可直接放入计算着色器RWStructuredBufferuint outputBuffer; cbuffer Globals { Ptruint g_inputData; uint g_inputDataLen; } // Calculate sum of half-open range [start, end) uint sumOfValues(uint *start, uint *end) { uint sum 0; for (uint *i start; i ! end; i) { sum sum *i; } return sum; } [numthreads(1, 1, 1)] void main(uint3 id : SV_DispatchThreadID) { // Calculate sum of elements 0, 1, ..., 9 provided // the input data buffer is big enough. outputBuffer[id.x] sumOfValues( g_inputData, g_inputData[min(g_inputDataLen, 10)]); }这段代码同时覆盖了多项核心能力Ptruint g_inputData使用泛型指针别名声明 uniform 数据指针uint *start, *end使用声明语法通过g_inputData[...]取址构造区间终点循环条件i ! end依赖末尾之后哨兵的合法比较i使用指针自增*i解引用读取元素。一致性测试 pointer-range-iteration-functional.slang 以解释执行方式验证了相同的区间迭代模式——对数组{1,2,3,4,5}以end arr[4] 1为哨兵求和CHECK 断言结果为15uint sumRange(uint* start, uint* end) { uint sum 0; for (uint* i start; i ! end; i) sum sum *i; return sum; } void main() { uint arr[5] { 1, 2, 3, 4, 5 }; uint* start arr[0]; uint* end arr[4] 1; // one past last — valid sentinel per doc uint result sumRange(start, end); //CHECK: 15 printf(%d\n, result); }一致性测试套件总览18 项声明的验证矩阵conformance/types-pointer 目录 下的测试套件把语言参考中的指针声明逐条映射为可执行测试claims 编号 C1–C18验证方式分为三类functional功能验证通常以TEST:INTERPRET(filecheckCHECK)解释执行并断言输出negative负向诊断验证以DIAGNOSTIC_TEST固定错误码E20017、E30019、E30079、E50100 等boundary边界行为验证如空指针、单元素步进、区间哨兵。声明Claim意图对应测试文件C1.T*声明 /*往返正确functionaldeclare-star-syntax-functional.slang、declare-star-syntax-emission.slangC2.T*与PtrT, Access.ReadWrite, AddressSpace.Device同类型functionalptr-star-equals-ptr-generic-functional.slang、pointer-default-access-mode-functional.slangC3.const T*被 E20017 拒绝negativeconst-pointer-rejected-negative.slangC4.PtrT泛型可声明、可解引用functionalptr-generic-type-functional.slangC5.ImmutablePtrT别名经显式 cast 可编译解引用functionalimmutableptr-type-functional.slangC6.obj产生有效指针*(obj) objfunctionaladdress-of-operator-functional.slangC7.__getAddress(obj)是的替代functionalgetaddress-builtin-functional.slangC8/C9.*p可读*p v可写functionaldereference-write-functional.slangC10.p-member与(*p).member等价functionalstruct-arrow-member-access-functional.slangC11.p n前进 n 元素functionalpointer-arithmetic-add-functional.slang、pointer-arithmetic-spirv-emission.slangC12.p - n后退 n 元素boundarypointer-arithmetic-sub-functional.slangC13.p/--p单元素步进boundarypointer-increment-functional.slangC14.nullptr空指针比较boundarynull-pointer-functional.slangC15/C18. 末尾之后哨兵 区间迭代求和正确functionalpointer-range-iteration-functional.slangC16. 默认地址空间 Device、默认访问模式 ReadWritefunctionalpointer-default-access-mode-functional.slangC17. 读写 → 只读无隐式转换E30019negativeno-implicit-rw-to-immutable-negative.slang指针作函数参数可修改调用方变量functionalpointer-pass-to-function-functional.slang未被测试覆盖的声明Untested claimsREADME 还诚实记录了当前无法通过自动化测试覆盖的声明及其原因主要包括局部内存指针仅 CUDA/CPU 支持验证 GPU 目标拒绝本地内存指针需要真实 GPU 驱动Vulkan/D3D12由 CI 覆盖不支持不透明句柄指针实际错误是 E30079关于不可变性而非专门的不透明句柄诊断属文档措辞不精确对齐加载/存储loadAligned()/storeAligned()定义在本文档之外无法从声明语法表面直接测试指针到接口的继承 cast编译器接受显式 cast仅当 code-gen 阶段尝试接口派发时才以 E50100 失败未定义行为类声明解引用无效指针、多维数组越界算术UB 本质上是规范性的编译器无需拒绝或标记按定义不可测试非 Device 地址空间如AddressSpace.GroupShared需要 GPU 运行环境与特定能力集HLSL/WGSL 发射当前两个后端会泄漏内部泛型或发射空地址空间属已知缺陷findings。文档缺口记录Doc gaps测试作者在验证过程中发现了两处文档措辞与实际行为不一致之处均记录在 README 的 Doc gaps observed 一节不支持不透明句柄指针表述模糊myTex全局Texture2D实际报 E30079读者无法区分错误源于不透明性还是全局不可变性建议文档说明不透明资源类型是隐式不可变对象或新增专门诊断。不能 cast 到指向接口的指针表述不准确cast 在 parse/check 阶段被接受失败发生在 code-gen 的接口派发阶段E50100建议文档澄清被拒绝的是动态派发而非 cast 本身。这两处记录对读者极具价值它们精确划定了文档语义与编译器实现之间的边界。小结Slang 的指针类型以T*声明语法为入口以PtrT, AccessMode, AddressSpace与ImmutablePtrT, AddressSpace两个标准库别名扩展了访问模式与地址空间的表达能力。取址/__getAddress、解引用*l-value 可写、成员访问-、指针算术/-//--、空指针与末尾之后哨兵构成了完整的指针语义默认AddressSpace.DeviceAccess.ReadWrite的 traits 规则与读写 → 只读无隐式转换E30019是类型安全的关键约束。由于slangc尚未完全实现指针实际使用时务必牢记局部内存指针仅限 CPU/CUDAconst T*会被 E20017 拒绝不透明句柄请使用DescriptorHandleTHLSL/WGSL 目标暂不可用。仓库中的一致性测试套件docs/generated/tests/conformance/types-pointer为文档的每一项声明提供了可执行、可断言的验证是理解与排查指针行为的首选参考资料。【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进