十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Slang 结构体(struct)语言参考与一致性测试全解析

Slang 结构体(struct)语言参考与一致性测试全解析 Slang 结构体struct语言参考与一致性测试全解析【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang导读本文以 Slang 语言参考文档 docs/language-reference/types-struct.md 为骨架完整讲解 Slang 中struct类型的语法、成员种类、构造与初始化、成员函数、属性property、下标subscript与函数调用运算符、内存布局以及This类型关键字等全部规范内容并以仓库中 docs/generated/tests/conformance/types-struct/ 目录下的 30 个一致性测试用例为佐证从语法规范到编译器行为逐一验证。读完本文你将掌握 Slang 结构体的完整语义、正确写法与常见误区并了解如何通过一致性测试用例验证这些语言行为。注本文涉及的所有.slang测试文件均位于docs/generated/tests/conformance/types-struct/目录其元信息//META:注释标注了对应的doc_ref锚点可直接追溯到语言参考原文。结构体声明语法四种声明形式Slang 的struct声明由文法规则struct-decl定义共有四种形式1. 无成员体no-body声明—— 仅声明结构体类型存在struct-decl [modifier-list] struct [identifier] [generic-params-decl] [: bases-clause] [ simple-type-spec] ;2. 带成员体with-members声明—— 定义结构体的布局与非布局成员struct-decl [modifier-list] struct [identifier] [generic-params-decl] [: bases-clause] (where where-clause)* { member-list }3. 链接期 extern 类型声明—— 声明定义于其他模块的结构体类型struct-decl [modifier-list] extern struct [identifier] [generic-params-decl] [: bases-clause] [ simple-type-spec] ;4. 链接期 export 类型别名声明—— 以类型别名形式导出结构体struct-decl [modifier-list] export struct [identifier] [generic-params-decl] [: bases-clause] simple-type-spec ;成员列表文法member-list ( var-decl | type-decl | function-decl | constructor-decl | property-decl | subscript-op-decl | function-call-op-decl )*参数说明语法单元含义modifier-list可选的修饰符列表包含访问控制public/internal/private与属性attributeidentifier结构体类型的可选名称generic-params-decl可选泛型参数声明见 genericsbases-clause可选的继承接口列表见 types-interfacesimple-type-spec可选的别名类型表达式用于模块相关语法where-clause可选的泛型约束表达式见 genericsmember-list结构体成员列表成员可以是成员变量、嵌套类型、成员函数、构造函数、属性、下标运算符或函数调用运算符已废弃语法struct关键字后的中括号属性列表⚠️Warning:在struct关键字之后使用中括号属性列表的语法已在 Slang 2025 中被弃用并在 Slang 2026 中移除。请将中括号属性放在struct关键字之前的modifier-list中参见 Issue #9691。即正确写法是[attribute] struct Foo { ... }而非struct [attribute] Foo { ... }。语义总览Description一个结构体structure是由一组有序成员构成的类型其四种声明形式分别对应不同的使用场景no-body 声明仅指定某名称的结构体类型存在使其可以出现在类型表达式中无需立即给出成员定义with-members 声明定义结构体类型包含布局及可扩展见 types-extension的非布局成员列表extern 声明指定一个在其他模块中定义的结构体类型export 声明将一个结构体以类型别名导出。命名结构体与匿名结构体当identifier指定时它是结构体的名称否则结构体为匿名结构体编译器会为其分配一个不指定的唯一名称。匿名结构体的主要用途是内联类型定义表达式例如struct { int a; } obj; // obj 的类型是一个匿名结构体含字段 int a;匿名结构体仅在 with-members 形式下有意义。一致性测试 struct-anonymous.slang 验证了内联匿名结构体的字段可通过点运算符访问并且在各后端HLSL/GLSL/Metal/WGSL/CUDA/CPP下均以SLANG_anonymous的名称发射。成员种类结构体成员在结构体体内声明共九种静态数据成员以static关键字声明的变量非静态数据成员字段以普通变量声明构造函数静态成员函数带static修饰符的函数非静态成员函数不带static修饰符的函数嵌套类型类型或类型别名声明property 声明__subscript声明函数调用运算符声明。static 关键字的存储语义数据成员和成员函数都可以用static关键字声明静态数据成员的存储分配自全局存储区global storage静态成员函数可以访问结构体的静态数据成员调用结构体的其他静态成员函数。非静态数据成员的存储分配为结构体实例的一部分非静态成员函数可以同时访问静态与非静态数据成员同时调用静态与非静态成员函数。一致性测试 struct-static-member-functional.slang 验证了静态数据成员在 HLSL 中发射为模块作用域静态变量C7struct-static-const-initializer.slang 验证了static const数据成员带默认初始化器时可经类型名访问并被常量折叠C8。默认初始化器规则数据成员可以携带默认初始化器规则如下使用初始化列表初始化对象时若列表未提供某非静态数据成员的值则使用该成员的默认初始化器使用构造函数初始化对象时默认初始化器指定数据成员的初始值构造函数可以覆盖该值const成员除外static const数据成员必须带有默认初始化器。一致性测试 struct-default-field-initializer.slang 验证 C22初始化列表场景struct-default-initializer-constructor-override.slang 验证 C23构造函数覆盖场景。顺序分配、嵌套类型、接口实现与扩展非静态数据成员在结构体类型变量被分配时按声明顺序顺序分配嵌套类型是封闭在外层结构体作用域内的普通类型通过外层结构体名访问结构体可以实现一个或多个 interface 类型结构体可以通过 type extension 被扩展。一致性测试 struct-nested-type.slang 验证 C9嵌套类型访问struct-interface-conformance.slang 验证 C11接口实现后可传入受该接口约束的泛型struct-generic.slang 验证带T泛型参数的结构体可在int/float上实例化。访问控制struct成员可以通过modifier-list中的访问控制说明符public、internal、private声明可见性默认成员可见性为internal从语言版本2026起无显式说明符的成员继承其外层struct的可见性因此public结构体的未修饰成员为public嵌套struct的成员可以访问外层struct的private成员。一致性测试 struct-access-visibility-default-internal.slang 验证了 C12显式public字段与默认可见性字段在public struct中均可从外部访问且二者在 HLSL/GLSL/SPIR-V/Metal/WGSL/CUDA/CPP 各后端发射时均保留为结构体成员。⚠️Warning:struct 继承 struct结构体之间的继承在 Slang 2025 及更早的语言版本中不稳定并已在 Slang 2026 中移除。请改用组合将结构体作为成员来代替。对象Objects对象object是struct的一个实例。一个实例由该结构体定义的所有非静态数据成员组成。数据成员可以通过初始化列表或构造函数初始化详见 变量声明。一致性测试 struct-aggregate-init.slang 验证 C13/C14实例字段是每对象一份初始化列表在构造时设置字段值。构造函数Constructors语法接口中使用的无函数体声明__init [generic-params-decl] ( param-list ) (where where-clause)* ;带函数体的声明__init [generic-params-decl] ( param-list ) (where where-clause)* { body-stmt* }语义规则当结构体定义了用户提供的构造函数时对象实例化时会执行构造函数构造函数可以有任意数量的参数构造函数没有返回类型可以定义多个构造函数此时根据初始化参数执行重载决议选出最合适的一个构造函数参数通过可选的初始化列表提供未提供初始化列表时调用无参构造函数若某非静态数据成员未被构造函数初始化则对象实例化后其状态未定义const数据成员不能被构造函数初始化where-clause为可选的泛型约束表达式。示例来自语言参考struct TestClass { int a, b; __init() { a 1; b 2; } __init(int _a) { a 1; b 2; } __init(int _a, int _b) { a _a; b _b; } } TestClass obj1; // obj1.a 1; // obj1.b 2; // // Note: TestClass obj1 { }; also calls the constructor // without parameters TestClass obj2 { 42 }; // obj2.a 42; // obj2.b 2; TestClass obj3 { 42, 43 }; // obj3.a 42; // obj3.b 43;一致性测试 struct-constructor-overload.slang 完整验证了 C15/C16/C17三个__init重载无参、单参、双参在-cpu运行时按参数个数正确分派且在 HLSL/GLSL/Metal/WGSL/CUDA/CPP 上均发射为TestClass_x24init_N形式的独立构造函数。聚合初始化无用户构造函数时当没有定义用户提供的构造函数时执行聚合初始化初始化列表按顺序为结构体的非静态数据成员提供值若初始化列表提供的值不足剩余数据成员被默认初始化若完全没有提供初始化列表则无用户构造函数的类以未定义状态实例化。一致性测试 struct-aggregate-init.slang 验证 C20/C21Plain p1 { 10, 20, 30 };全量填充Plain p2 { 5, 6 };第三个字段z被默认初始化为 0CHECK 输出为 10/20/30/5/6/0。Remark 1:当无用户构造函数的类在无初始化列表的情况下实例化时对象初始状态未定义。这包括其数据成员本身带有用户提供构造函数的成员子结构体TestField的情况。外层结构体的字段状态仍是未定义的无论TestField是否有用户构造函数。struct TestField { int x; __init() { x 5; } } struct TestClass { int a, b; TestField f; } // note: obj is instantiated with an undefined state // regardless of TestField having a user-provided constructor. TestClass obj;Remark 2:访问处于未定义状态的数据成员属于未定义行为。负向测试const 成员不可被构造函数初始化一致性测试 struct-constructor-no-const-member.slang 验证 C19在__init内给const数据成员赋值是编译错误E30011。静态成员函数Static Member Functions静态成员函数是封闭在结构体名字空间内的普通函数静态成员函数只能访问静态结构体成员调用静态成员函数不需要对象。一致性测试 struct-static-member-function.slang 验证 C24/C25静态成员函数无需对象即可调用并在 HLSL 中发射为自由函数。非静态成员函数Non-static Member Functions非静态成员函数有一个隐藏参数this它引用对象该隐藏参数用于引用对象数据成员以及调用其他非静态成员函数函数体内其他成员可以使用this.引用但可省略默认情况下成员函数对对象成员只有只读访问权限若需写访问成员函数必须声明[mutating]属性非静态成员函数不能在没有对象的情况下调用。Remark:用 C 术语来说成员函数默认是const的。[mutating]属性使其变为非const成员函数。综合示例来自语言参考struct TestStruct { // data member int a; // static data member, initial value 5 static int b 5; // static constant data member, initial value 6 static const int c 6; // nested type struct NestedStruct { static int c 6; int d; } // member function with read-only access int getA() { // also just plain return a would do return this.a; } // member function with read/write access [mutating] int incrementAndReturnA() { a a 1; return a; } // static member function static int getB() { return b; } static int incrementAndReturnB() { // [mutating] not needed for modifying static member b b 1; return b; } } TestStruct obj { }; TestStruct.NestedStruct obj2 { }; obj.a 42; int tmp0 TestStruct.b TestStruct.NestedStruct.c; int tmp1 obj.getA(); int tmp2 obj.incrementAndReturnA(); int tmp3 TestStruct.getB(); int tmp4 TestStruct::incrementAndReturnB();注意::等价于.用于静态成员访问但推荐使用.::已废弃。一致性测试对[mutating]的验证struct-this-implicit-optional.slang 验证 C26this.member与裸member访问同一字段struct-member-function-readonly.slang 验证 C27非[mutating]成员函数读取字段正确struct-mutating-member-function.slang 验证 C28[mutating]成员函数写入非静态字段且变异在实例上可观察从源码可见该测试还在各后端验证了this参数的类型限定符——HLSL/GLSL 发射为inout TestStruct this_NMetal 为thread*WGSL 为ptrfunction, ...CUDA/CPP 为指针struct-mutating-rejected-without-attribute.slang 验证 C29在非[mutating]函数中写入非静态成员触发编译错误E30011非 l-value。属性Properties语法现代语法、隐式get声明仅接口中property identifier : simple-type-spec ;现代语法、显式访问器声明property identifier : simple-type-spec { accessor-decl* }传统语法、隐式get声明仅接口中property traditional-var-decl ;传统语法、显式访问器声明property traditional-var-decl { accessor-decl* }访问器声明语法无函数体仅接口中accessor-decl (get | set) [( param-list )] ;访问器声明语法带函数体accessor-decl (get | set) [( param-list )] { body-stmt* }语义规则property 是非静态成员提供数据成员访问接口读取 property 时定向到get访问器写入时定向到set访问器只读 property仅有get访问器只写 property仅有set访问器读写 property两者都有get访问器声明中的括号可选get不接受参数set访问器声明中的括号和参数可选未指定参数时set体内自动提供与 property 同类型的隐式参数newValue无访问器或访问器无函数体的 property 声明形式仅在 interface 声明中有用。⚠️Warning:property 引用访问器ref是 Slang 内部语言特性可能发生变化使用时可能无法按预期工作。示例来自语言参考struct TestClass { float m_val; bool m_valIsPositive; property someProp : float { get { return m_val; } set { m_val newValue; m_valIsPositive (newValue 0.0f); } } } [shader(compute)] void main(uint3 id : SV_DispatchThreadID) { TestClass obj { }; // this sets both obj.m_val and obj.m_valIsPositive obj.someProp 3.0f; }Remark 1:当希望系统化地在数据成员访问上附加额外逻辑时property 可用来替代非static数据成员从而避免重构调用点。Remark 2:非静态数据成员可以用来实现接口的 property 要求详见 interfaces。Remark 3:上例中 property 也可声明为get()/set(float newVal)的显式参数形式见 struct-property-explicit-param.slang。一致性测试验证struct-property-get-set.slang 验证 C30/C32读走get、写走set且set体内使用隐式newValue运行输出确认m_val与m_valIsPositive副作用一致3 / 1 / 0 / -1struct-property-readonly.slang 验证 C31仅get的只读 property 在 HLSL 中正确发射struct-property-explicit-param.slang 验证 C33显式参数名在set体内替代newValue。成员与嵌套类型的访问静态/非静态成员及嵌套类型均使用.运算符访问。⚠️Warning:C 风格的作用域解析运算符::已废弃不应再使用。一致性测试 struct-dot-member-access.slang 验证 C34点运算符访问各类成员与嵌套类型struct-members-full-example-emission.slang 将语言参考中的完整综合示例发射到 HLSL 验证可编译性C35 的::废弃告警行为因规范未明示告警代码而未纳入测试。下标运算符Subscript Operator语法隐式get声明仅接口中__subscript ( param-list ) - simple-type-spec ;显式访问器声明__subscript ( param-list ) - simple-type-spec { accessor-decl* }accessor-decl语法与 property 相同。语义规则结构体可通过__subscript声明添加下标[]运算符概念上与property类似主要区别是它作用于struct 实例本身而非成员并且接受参数下标声明可以有任意数量的参数包括零个参数读取时调用get访问器get声明中的括号可选赋值时调用set访问器括号和参数可选未指定时提供隐式newValue类型与下标返回类型相同允许多个__subscript声明只要签名不同重载决议与函数调用一致。⚠️Warning:下标运算符引用访问器ref是 Slang 内部语言特性可能发生变化使用时可能无法按预期工作。示例来自语言参考RWStructuredBufferfloat outputBuffer; struct TestStruct { var arr : float[10][10]; // 0-parameter subscript operator __subscript () - float { get { return arr[0][0]; } set { arr[0][0] newValue; } } // 1-parameter subscript operator __subscript (int i) - float { get { return arr[0][i]; } set { arr[0][i] newValue; } } // 2-parameter subscript operator __subscript (int i0, int i1) - float { get { return arr[i1][i0]; } set { arr[i1][i0] newValue; } } } [numthreads(1,1,1)] void main(uint3 id : SV_DispatchThreadID) { TestStruct x { }; x[] id.z; x[id.y] id.z; x[id.x, id.y] id.z; outputBuffer[id.x] x[]; outputBuffer[id.y] x[id.x]; outputBuffer[id.z] x[id.x, id.y]; }一致性测试 struct-subscript-multi-param.slang 验证 C37/C38零参数与单参数__subscript重载共存m[]与m[1]分别正确分派输出 100 / 200struct-subscript-op-functional.slang 验证 C36 的读写路径。函数调用运算符Function Call Operator语法无函数体声明仅接口中simple-type-spec operator ( ) ( param-list ) ;带函数体声明simple-type-spec operator ( ) ( param-list ) { body-stmt* }语义规则通过operator ()声明可以添加函数调用()运算符使对象可以像函数一样被调用允许多个声明只要签名不同重载决议与函数调用一致。示例来自语言参考RWStructuredBufferfloat outputBuffer; struct TestStruct { float base; float operator () () { return base; } float operator () (uint x) { return base * float(x); } float operator () (uint x, uint y) { return base * float(x) * float(y); } } void main(uint3 id : SV_DispatchThreadID) { TestStruct obj { 42.0f }; outputBuffer[0] obj(); outputBuffer[0] obj(id.y); outputBuffer[0] obj(id.z, id.z * 2); }一致性测试 struct-call-op-functional.slang 验证 C39/C40多个operator ()重载按参数个数正确分派。内存布局Memory Layout自然布局Natural Layout自然布局是结构体类型的基准布局规则如下结构体的对齐是 1、任意成员对齐、任意父类型对齐三者的最大值数据按如下顺序排列父类型 → 非静态数据成员数据项的偏移第一项偏移为 0第N项的偏移 前一项的偏移大小再向上取整到该项的对齐结构体的大小 最后一项的偏移大小即不做尾填充不向上取整到结构体对齐。以下算法可实现自然布局1. 初始化变量 size 0alignment 1 2. 对结构体类型的每个字段 f a. alignment max(alignment, f 的对齐) b. size 不小于 size 的 alignment 的最小倍数 c. 字段 f 的偏移 size d. size f 的大小 3. 完成后size 与 alignment 即结构体类型的大小与对齐Remark:大多数目标平台并不直接使用自然布局但它为定义其他布局提供了基线。任何结构体布局都必须保证对齐至少不小于标准布局。一致性测试 struct-natural-layout-emission.slang 验证 C41/C10int x; float y; int z;在 HLSL/GLSL/Metal/WGSL/CUDA/CPP 均按声明顺序发射SPIR-V 层面还验证了OpMemberName索引 0/1/2 与OpTypeStruct %int %float %int的成员顺序在-O0下钉住避免优化折叠。C 风格布局C-Style LayoutC 风格布局与自然布局的区别在于结构体大小向上取整到结构体对齐即增加尾填充这与典型 C/C 编译器的布局规则一致。注意该行为无法仅通过 slangc CLI 观察需要反射 API 查询结构体大小与对齐因此一致性套件将其标记为needs-unit-test未纳入运行时测试见 README 中 C42 的说明。D3D 常量缓冲区布局D3D Constant Buffer LayoutD3D 常量缓冲区布局与自然布局类似但有两点差异最小对齐为 16若某数据成员跨过 16 字节边界且其偏移未按 16 对齐则将该偏移向上取整到下一个 16 的倍数——在 HLSL 中这被称为improper straddle不当跨越。注意D3D 布局细节需要特定 GPU 层级的测试且字节偏移/对齐数值无法仅从 slangc CLI 观察需反射 API 或 cbuffer 布局查询因此一致性套件未在运行时测试中覆盖README 中 C43 的说明。This 类型关键字在结构体或接口声明的函数体内关键字This可用于引用封闭类型在结构体类型声明内This指结构体类型本身在接口声明内This指符合该接口的具体类型即this的类型。一致性测试 struct-This-type-keyword.slang 验证 C44成员函数的返回类型与局部变量均使用This运行输出确认返回的实例携带期望值CHECK: 99并在各后端正确发射。一致性测试套件总览与未覆盖项README.md 记录了该一致性套件的完整方法论覆盖策略每个语言参考声明claim配一个功能测试INTERPRET 运行时或 emit 发射测试沿文档标注的维度展开成员种类、可见性、构造重载、访问器形式、下标元数、调用运算符重载、内存布局、泛型结构体、接口实现、嵌套类型、This关键字负向测试覆盖“向只读成员写入”与“构造函数中写 const 成员”静态数据成员仅通过 HLSL 发射测试因为 slangi VM 字节码发射器不支持模块作用域全局指令slangi-vm-global-var-bytecode-unsupported未测试声明及其原因C2/C5extern struct/export struct需要跨文件模块链接needs-multi-file-testC18构造函数未初始化成员为未定义状态属未定义行为无法断言C42/C43布局数值需要反射 API 或单元测试needs-unit-testC35::废弃告警行为未在规范中明示where子句泛型由 generics 套件覆盖ref访问器因属内部特性不纳入测试。文档缺口Doc gaps observedREADME 还记录了撰写该套件时发现的文档缺口no-body 声明与后续定义语言参考称 no-body 声明“使类型可先用于类型表达式”但编译器并不将 no-body 声明与同名 with-members 声明合并——struct ForwardDeclared;后跟struct ForwardDeclared { int x; }会报error[E30200] conflicting declaration及error[E39999] ambiguous referencehlsl/glsl/spirv-asm/metal/wgsl 行为一致This小节缺少稳定锚点文档末尾 This Type 小节没有独立的#this-type锚点无法被 doc_ref 精确引用构造函数未定义状态的示例缺失文档 Remark 1 缺少一个“外层无构造函数、内层字段类型带__init”的独立最小示例。结语从语言参考 types-struct.md 到一致性测试 conformance/types-struct本文完整覆盖了 Slang 结构体的全部规范维度四种声明形式、九类成员、static存储语义、初始化与聚合初始化、构造重载、this/[mutating]读写规则、property/subscript/call-op 三类访问器成员、三种内存布局算法、访问控制与This关键字。每个规范点都有对应的源码级测试证据如E30011l-value 错误、inout/thread*/ptrfunction,...的this参数发射差异、SPIR-VOpTypeStruct成员顺序既可作为编写 Slang 着色器的速查手册也可作为阅读编译器行为的入口参考。【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表