@@ -38,98 +38,160 @@ impl<'w> BlockContext<'w> {
38
38
///
39
39
/// Given `array`, an expression referring a runtime-sized array, return the
40
40
/// instruction id for the array's length.
41
+ ///
42
+ /// Runtime-sized arrays may only appear in the values of global
43
+ /// variables, which must have one of the following Naga types:
44
+ /// 1) A runtime-sized array.
45
+ /// 2) A struct whose last member is a runtime-sized array.
46
+ /// 3) A binding array of 2).
47
+ ///
48
+ /// Thus, the expression `array` has the form of:
49
+ /// - An optional [`AccessIndex`], for case 2), applied to...
50
+ /// - An optional [`Access`] or [`AccessIndex`], for case 3), applied to...
51
+ /// - A [`GlobalVariable`].
52
+ ///
53
+ /// The SPIR-V generated takes into account wrapped globals; see
54
+ /// [`global_needs_wrapper`].
55
+ ///
56
+ /// [`GlobalVariable`]: crate::Expression::GlobalVariable
57
+ /// [`AccessIndex`]: crate::Expression::AccessIndex
58
+ /// [`Access`]: crate::Expression::Access
59
+ /// [`base`]: crate::Expression::Access::base
41
60
pub ( super ) fn write_runtime_array_length (
42
61
& mut self ,
43
62
array : Handle < crate :: Expression > ,
44
63
block : & mut Block ,
45
64
) -> Result < Word , Error > {
46
- // Naga IR permits runtime-sized arrays as global variables, or as the
47
- // final member of a struct that is a global variable, or one of these
48
- // inside a buffer that is itself an element in a buffer bindings array.
49
- // SPIR-V requires that runtime-sized arrays are wrapped in structs.
50
- // See `helpers::global_needs_wrapper` and its uses.
51
- let ( opt_array_index_id, global_handle, opt_last_member_index) = match self
52
- . ir_function
53
- . expressions [ array]
54
- {
65
+ // The index into the binding array, if any.
66
+ let binding_array_index_id: Option < Word > ;
67
+
68
+ // The handle to the Naga IR global we're referring to.
69
+ let global_handle: Handle < crate :: GlobalVariable > ;
70
+
71
+ // At the Naga type level, if the runtime-sized array is the final member of a
72
+ // struct, this is that member's index.
73
+ //
74
+ // This does not cover wrappers: if this backend wrapped the Naga global's
75
+ // type in a synthetic SPIR-V struct (see `global_needs_wrapper`), this is
76
+ // `None`.
77
+ let opt_last_member_index: Option < u32 > ;
78
+
79
+ // Inspect `array` and decide whether we have a binding array and/or an
80
+ // enclosing struct.
81
+ match self . ir_function . expressions [ array] {
55
82
crate :: Expression :: AccessIndex { base, index } => {
56
83
match self . ir_function . expressions [ base] {
57
- // The global variable is an array of buffer bindings of structs,
58
- // we are accessing one of them with a static index,
59
- // and the last member of it.
60
84
crate :: Expression :: AccessIndex {
61
85
base : base_outer,
62
86
index : index_outer,
63
87
} => match self . ir_function . expressions [ base_outer] {
88
+ // An `AccessIndex` of an `AccessIndex` must be a
89
+ // binding array holding structs whose last members are
90
+ // runtime-sized arrays.
64
91
crate :: Expression :: GlobalVariable ( handle) => {
65
92
let index_id = self . get_index_constant ( index_outer) ;
66
- ( Some ( index_id) , handle, Some ( index) )
93
+ binding_array_index_id = Some ( index_id) ;
94
+ global_handle = handle;
95
+ opt_last_member_index = Some ( index) ;
96
+ }
97
+ _ => {
98
+ return Err ( Error :: Validation (
99
+ "array length expression: AccessIndex(AccessIndex(Global))" ,
100
+ ) )
67
101
}
68
- _ => return Err ( Error :: Validation ( "array length expression case-1a" ) ) ,
69
102
} ,
70
- // The global variable is an array of buffer bindings of structs,
71
- // we are accessing one of them with a dynamic index,
72
- // and the last member of it.
73
103
crate :: Expression :: Access {
74
104
base : base_outer,
75
105
index : index_outer,
76
106
} => match self . ir_function . expressions [ base_outer] {
107
+ // Similarly, an `AccessIndex` of an `Access` must be a
108
+ // binding array holding structs whose last members are
109
+ // runtime-sized arrays.
77
110
crate :: Expression :: GlobalVariable ( handle) => {
78
111
let index_id = self . cached [ index_outer] ;
79
- ( Some ( index_id) , handle, Some ( index) )
112
+ binding_array_index_id = Some ( index_id) ;
113
+ global_handle = handle;
114
+ opt_last_member_index = Some ( index) ;
115
+ }
116
+ _ => {
117
+ return Err ( Error :: Validation (
118
+ "array length expression: AccessIndex(Access(Global))" ,
119
+ ) )
80
120
}
81
- _ => return Err ( Error :: Validation ( "array length expression case-1b" ) ) ,
82
121
} ,
83
- // The global variable is a buffer, and we are accessing the last member.
84
122
crate :: Expression :: GlobalVariable ( handle) => {
85
- let global = & self . ir_module . global_variables [ handle ] ;
86
- match self . ir_module . types [ global . ty ] . inner {
87
- // The global variable is an array of buffer bindings of run-time arrays.
88
- crate :: TypeInner :: BindingArray { .. } => ( Some ( index ) , handle , None ) ,
89
- // The global variable is a struct, and we are accessing the last member
90
- _ => ( None , handle, Some ( index ) ) ,
91
- }
123
+ // An outer `AccessIndex` applied directly to a
124
+ // `GlobalVariable`. Since binding arrays can only contain
125
+ // structs, this must be referring to the last member of a
126
+ // struct that is a runtime-sized array.
127
+ binding_array_index_id = None ;
128
+ global_handle = handle;
129
+ opt_last_member_index = Some ( index ) ;
92
130
}
93
- _ => return Err ( Error :: Validation ( "array length expression case-1c" ) ) ,
94
- }
95
- }
96
- // The global variable is an array of buffer bindings of arrays.
97
- crate :: Expression :: Access { base, index } => match self . ir_function . expressions [ base] {
98
- crate :: Expression :: GlobalVariable ( handle) => {
99
- let index_id = self . cached [ index] ;
100
- let global = & self . ir_module . global_variables [ handle] ;
101
- match self . ir_module . types [ global. ty ] . inner {
102
- crate :: TypeInner :: BindingArray { .. } => ( Some ( index_id) , handle, None ) ,
103
- _ => return Err ( Error :: Validation ( "array length expression case-2a" ) ) ,
131
+ _ => {
132
+ return Err ( Error :: Validation (
133
+ "array length expression: AccessIndex(<unexpected>)" ,
134
+ ) )
104
135
}
105
136
}
106
- _ => return Err ( Error :: Validation ( "array length expression case-2b" ) ) ,
107
- } ,
108
- // The global variable is a run-time array.
137
+ }
109
138
crate :: Expression :: GlobalVariable ( handle) => {
110
- let global = & self . ir_module . global_variables [ handle ] ;
111
- if ! global_needs_wrapper ( self . ir_module , global ) {
112
- return Err ( Error :: Validation ( "array length expression case-3" ) ) ;
113
- }
114
- ( None , handle , None )
139
+ // A direct reference to a global variable. This must hold the
140
+ // runtime-sized array directly.
141
+ binding_array_index_id = None ;
142
+ global_handle = handle ;
143
+ opt_last_member_index = None ;
115
144
}
116
145
_ => return Err ( Error :: Validation ( "array length expression case-4" ) ) ,
117
146
} ;
118
147
148
+ // The verifier should have checked this, but make sure the inspection above
149
+ // agrees with the type about whether a binding array is involved.
150
+ //
151
+ // Eventually we do want to support `binding_array<array<T>>`. This check
152
+ // ensures that whoever relaxes the validator will get an error message from
153
+ // us, not just bogus SPIR-V.
154
+ let global = & self . ir_module . global_variables [ global_handle] ;
155
+ match (
156
+ & self . ir_module . types [ global. ty ] . inner ,
157
+ binding_array_index_id,
158
+ ) {
159
+ ( & crate :: TypeInner :: BindingArray { .. } , Some ( _) ) => { }
160
+ ( _, None ) => { }
161
+ _ => {
162
+ return Err ( Error :: Validation (
163
+ "array length expression: bad binding array inference" ,
164
+ ) )
165
+ }
166
+ }
167
+
168
+ // SPIR-V allows runtime-sized arrays to appear only as the last member of a
169
+ // struct. Determine this member's index.
119
170
let gvar = self . writer . global_variables [ global_handle] . clone ( ) ;
120
171
let global = & self . ir_module . global_variables [ global_handle] ;
121
- let ( last_member_index, gvar_id) = match opt_last_member_index {
122
- Some ( index) => ( index, gvar. access_id ) ,
123
- None => {
124
- if !global_needs_wrapper ( self . ir_module , global) {
125
- return Err ( Error :: Validation (
126
- "pointer to a global that is not a wrapped array" ,
127
- ) ) ;
128
- }
172
+ let needs_wrapper = global_needs_wrapper ( self . ir_module , global) ;
173
+ let ( last_member_index, gvar_id) = match ( opt_last_member_index, needs_wrapper) {
174
+ ( Some ( index) , false ) => {
175
+ // At the Naga type level, the runtime-sized array appears as the
176
+ // final member of a struct, whose index is `index`. We didn't need to
177
+ // wrap this, since the Naga type meets SPIR-V's requirements already.
178
+ ( index, gvar. access_id )
179
+ }
180
+ ( None , true ) => {
181
+ // At the Naga type level, the runtime-sized array does not appear
182
+ // within a struct. We wrapped this in an OpTypeStruct with nothing
183
+ // else in it, so the index is zero. OpArrayLength wants the pointer
184
+ // to the wrapper struct, so use `gvar.var_id`.
129
185
( 0 , gvar. var_id )
130
186
}
187
+ _ => {
188
+ return Err ( Error :: Validation (
189
+ "array length expression: bad SPIR-V wrapper struct inference" ,
190
+ ) ) ;
191
+ }
131
192
} ;
132
- let structure_id = match opt_array_index_id {
193
+
194
+ let structure_id = match binding_array_index_id {
133
195
// We are indexing inside a binding array, generate the access op.
134
196
Some ( index_id) => {
135
197
let element_type_id = match self . ir_module . types [ global. ty ] . inner {
0 commit comments