1 // Copyright 2021 The Tint Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "src/resolver/resolver.h"
16
17 #include "gmock/gmock.h"
18 #include "src/resolver/resolver_test_helper.h"
19
20 namespace tint {
21 namespace resolver {
22 namespace {
23
24 using ResolverStorageClassLayoutValidationTest = ResolverTest;
25
26 // Detect unaligned member for storage buffers
TEST_F(ResolverStorageClassLayoutValidationTest,StorageBuffer_UnalignedMember)27 TEST_F(ResolverStorageClassLayoutValidationTest,
28 StorageBuffer_UnalignedMember) {
29 // [[block]]
30 // struct S {
31 // [[size(5)]] a : f32;
32 // [[align(1)]] b : f32;
33 // };
34 // [[group(0), binding(0)]]
35 // var<storage> a : S;
36
37 Structure(Source{{12, 34}}, "S",
38 {Member("a", ty.f32(), {MemberSize(5)}),
39 Member(Source{{34, 56}}, "b", ty.f32(), {MemberAlign(1)})},
40 {StructBlock()});
41
42 Global(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage,
43 GroupAndBinding(0, 0));
44
45 ASSERT_FALSE(r()->Resolve());
46 EXPECT_EQ(
47 r()->error(),
48 R"(34:56 error: the offset of a struct member of type 'f32' in storage class 'storage' must be a multiple of 4 bytes, but 'b' is currently at offset 5. Consider setting [[align(4)]] on this member
49 12:34 note: see layout of struct:
50 /* align(4) size(12) */ struct S {
51 /* offset(0) align(4) size( 5) */ a : f32;
52 /* offset(5) align(1) size( 4) */ b : f32;
53 /* offset(9) align(1) size( 3) */ // -- implicit struct size padding --;
54 /* */ };
55 78:90 note: see declaration of variable)");
56 }
57
TEST_F(ResolverStorageClassLayoutValidationTest,StorageBuffer_UnalignedMember_SuggestedFix)58 TEST_F(ResolverStorageClassLayoutValidationTest,
59 StorageBuffer_UnalignedMember_SuggestedFix) {
60 // [[block]]
61 // struct S {
62 // [[size(5)]] a : f32;
63 // [[align(4)]] b : f32;
64 // };
65 // [[group(0), binding(0)]]
66 // var<storage> a : S;
67
68 Structure(Source{{12, 34}}, "S",
69 {Member("a", ty.f32(), {MemberSize(5)}),
70 Member(Source{{34, 56}}, "b", ty.f32(), {MemberAlign(4)})},
71 {StructBlock()});
72
73 Global(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage,
74 GroupAndBinding(0, 0));
75
76 ASSERT_TRUE(r()->Resolve()) << r()->error();
77 }
78
79 // Detect unaligned struct member for uniform buffers
TEST_F(ResolverStorageClassLayoutValidationTest,UniformBuffer_UnalignedMember_Struct)80 TEST_F(ResolverStorageClassLayoutValidationTest,
81 UniformBuffer_UnalignedMember_Struct) {
82 // struct Inner {
83 // scalar : i32;
84 // };
85 //
86 // [[block]]
87 // struct Outer {
88 // scalar : f32;
89 // inner : Inner;
90 // };
91 //
92 // [[group(0), binding(0)]]
93 // var<uniform> a : Outer;
94
95 Structure(Source{{12, 34}}, "Inner", {Member("scalar", ty.i32())});
96
97 Structure(Source{{34, 56}}, "Outer",
98 {
99 Member("scalar", ty.f32()),
100 Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
101 },
102 {StructBlock()});
103
104 Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
105 ast::StorageClass::kUniform, GroupAndBinding(0, 0));
106
107 ASSERT_FALSE(r()->Resolve());
108 EXPECT_EQ(
109 r()->error(),
110 R"(56:78 error: the offset of a struct member of type 'Inner' in storage class 'uniform' must be a multiple of 16 bytes, but 'inner' is currently at offset 4. Consider setting [[align(16)]] on this member
111 34:56 note: see layout of struct:
112 /* align(4) size(8) */ struct Outer {
113 /* offset(0) align(4) size(4) */ scalar : f32;
114 /* offset(4) align(4) size(4) */ inner : Inner;
115 /* */ };
116 12:34 note: and layout of struct member:
117 /* align(4) size(4) */ struct Inner {
118 /* offset(0) align(4) size(4) */ scalar : i32;
119 /* */ };
120 78:90 note: see declaration of variable)");
121 }
122
TEST_F(ResolverStorageClassLayoutValidationTest,UniformBuffer_UnalignedMember_Struct_SuggestedFix)123 TEST_F(ResolverStorageClassLayoutValidationTest,
124 UniformBuffer_UnalignedMember_Struct_SuggestedFix) {
125 // struct Inner {
126 // scalar : i32;
127 // };
128 //
129 // [[block]]
130 // struct Outer {
131 // scalar : f32;
132 // [[align(16)]] inner : Inner;
133 // };
134 //
135 // [[group(0), binding(0)]]
136 // var<uniform> a : Outer;
137
138 Structure(Source{{12, 34}}, "Inner", {Member("scalar", ty.i32())});
139
140 Structure(Source{{34, 56}}, "Outer",
141 {
142 Member("scalar", ty.f32()),
143 Member(Source{{56, 78}}, "inner", ty.type_name("Inner"),
144 {MemberAlign(16)}),
145 },
146 {StructBlock()});
147
148 Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
149 ast::StorageClass::kUniform, GroupAndBinding(0, 0));
150
151 ASSERT_TRUE(r()->Resolve()) << r()->error();
152 }
153
154 // Detect unaligned array member for uniform buffers
TEST_F(ResolverStorageClassLayoutValidationTest,UniformBuffer_UnalignedMember_Array)155 TEST_F(ResolverStorageClassLayoutValidationTest,
156 UniformBuffer_UnalignedMember_Array) {
157 // type Inner = [[stride(16)]] array<f32, 10>;
158 //
159 // [[block]]
160 // struct Outer {
161 // scalar : f32;
162 // inner : Inner;
163 // };
164 //
165 // [[group(0), binding(0)]]
166 // var<uniform> a : Outer;
167 Alias("Inner", ty.array(ty.f32(), 10, 16));
168
169 Structure(Source{{12, 34}}, "Outer",
170 {
171 Member("scalar", ty.f32()),
172 Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
173 },
174 {StructBlock()});
175
176 Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
177 ast::StorageClass::kUniform, GroupAndBinding(0, 0));
178
179 ASSERT_FALSE(r()->Resolve());
180 EXPECT_EQ(
181 r()->error(),
182 R"(56:78 error: the offset of a struct member of type '[[stride(16)]] array<f32, 10>' in storage class 'uniform' must be a multiple of 16 bytes, but 'inner' is currently at offset 4. Consider setting [[align(16)]] on this member
183 12:34 note: see layout of struct:
184 /* align(4) size(164) */ struct Outer {
185 /* offset( 0) align(4) size( 4) */ scalar : f32;
186 /* offset( 4) align(4) size(160) */ inner : [[stride(16)]] array<f32, 10>;
187 /* */ };
188 78:90 note: see declaration of variable)");
189 }
190
TEST_F(ResolverStorageClassLayoutValidationTest,UniformBuffer_UnalignedMember_Array_SuggestedFix)191 TEST_F(ResolverStorageClassLayoutValidationTest,
192 UniformBuffer_UnalignedMember_Array_SuggestedFix) {
193 // type Inner = [[stride(16)]] array<f32, 10>;
194 //
195 // [[block]]
196 // struct Outer {
197 // scalar : f32;
198 // [[align(16)]] inner : Inner;
199 // };
200 //
201 // [[group(0), binding(0)]]
202 // var<uniform> a : Outer;
203 Alias("Inner", ty.array(ty.f32(), 10, 16));
204
205 Structure(Source{{12, 34}}, "Outer",
206 {
207 Member("scalar", ty.f32()),
208 Member(Source{{34, 56}}, "inner", ty.type_name("Inner"),
209 {MemberAlign(16)}),
210 },
211 {StructBlock()});
212
213 Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
214 ast::StorageClass::kUniform, GroupAndBinding(0, 0));
215
216 ASSERT_TRUE(r()->Resolve()) << r()->error();
217 }
218
219 // Detect uniform buffers with byte offset between 2 members that is not a
220 // multiple of 16 bytes
TEST_F(ResolverStorageClassLayoutValidationTest,UniformBuffer_MembersOffsetNotMultipleOf16)221 TEST_F(ResolverStorageClassLayoutValidationTest,
222 UniformBuffer_MembersOffsetNotMultipleOf16) {
223 // struct Inner {
224 // [[align(1), size(5)]] scalar : i32;
225 // };
226 //
227 // [[block]]
228 // struct Outer {
229 // inner : Inner;
230 // scalar : i32;
231 // };
232 //
233 // [[group(0), binding(0)]]
234 // var<uniform> a : Outer;
235
236 Structure(Source{{12, 34}}, "Inner",
237 {Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)})});
238
239 Structure(Source{{34, 56}}, "Outer",
240 {
241 Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
242 Member(Source{{78, 90}}, "scalar", ty.i32()),
243 },
244 {StructBlock()});
245
246 Global(Source{{22, 24}}, "a", ty.type_name("Outer"),
247 ast::StorageClass::kUniform, GroupAndBinding(0, 0));
248
249 ASSERT_FALSE(r()->Resolve());
250 EXPECT_EQ(
251 r()->error(),
252 R"(78:90 error: uniform storage requires that the number of bytes between the start of the previous member of type struct and the current member be a multiple of 16 bytes, but there are currently 8 bytes between 'inner' and 'scalar'. Consider setting [[align(16)]] on this member
253 34:56 note: see layout of struct:
254 /* align(4) size(12) */ struct Outer {
255 /* offset( 0) align(1) size( 5) */ inner : Inner;
256 /* offset( 5) align(1) size( 3) */ // -- implicit field alignment padding --;
257 /* offset( 8) align(4) size( 4) */ scalar : i32;
258 /* */ };
259 12:34 note: and layout of previous member struct:
260 /* align(1) size(5) */ struct Inner {
261 /* offset(0) align(1) size(5) */ scalar : i32;
262 /* */ };
263 22:24 note: see declaration of variable)");
264 }
265
TEST_F(ResolverStorageClassLayoutValidationTest,UniformBuffer_MembersOffsetNotMultipleOf16_SuggestedFix)266 TEST_F(ResolverStorageClassLayoutValidationTest,
267 UniformBuffer_MembersOffsetNotMultipleOf16_SuggestedFix) {
268 // struct Inner {
269 // [[align(1), size(5)]] scalar : i32;
270 // };
271 //
272 // [[block]]
273 // struct Outer {
274 // [[align(16)]] inner : Inner;
275 // scalar : i32;
276 // };
277 //
278 // [[group(0), binding(0)]]
279 // var<uniform> a : Outer;
280
281 Structure(Source{{12, 34}}, "Inner",
282 {Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)})});
283
284 Structure(Source{{34, 56}}, "Outer",
285 {
286 Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
287 Member(Source{{78, 90}}, "scalar", ty.i32(), {MemberAlign(16)}),
288 },
289 {StructBlock()});
290
291 Global(Source{{22, 34}}, "a", ty.type_name("Outer"),
292 ast::StorageClass::kUniform, GroupAndBinding(0, 0));
293
294 ASSERT_TRUE(r()->Resolve()) << r()->error();
295 }
296
297 // Make sure that this doesn't fail validation because vec3's align is 16, but
298 // size is 12. 's' should be at offset 12, which is okay here.
TEST_F(ResolverStorageClassLayoutValidationTest,UniformBuffer_Vec3MemberOffset_NoFail)299 TEST_F(ResolverStorageClassLayoutValidationTest,
300 UniformBuffer_Vec3MemberOffset_NoFail) {
301 // [[block]]
302 // struct ScalarPackedAtEndOfVec3 {
303 // v : vec3<f32>;
304 // s : f32;
305 // };
306 // [[group(0), binding(0)]]
307 // var<uniform> a : ScalarPackedAtEndOfVec3;
308
309 Structure("ScalarPackedAtEndOfVec3",
310 {
311 Member("v", ty.vec3(ty.f32())),
312 Member("s", ty.f32()),
313 },
314 {StructBlock()});
315
316 Global(Source{{78, 90}}, "a", ty.type_name("ScalarPackedAtEndOfVec3"),
317 ast::StorageClass::kUniform, GroupAndBinding(0, 0));
318
319 ASSERT_TRUE(r()->Resolve()) << r()->error();
320 }
321
322 // Detect array stride must be a multiple of 16 bytes for uniform buffers
TEST_F(ResolverStorageClassLayoutValidationTest,UniformBuffer_InvalidArrayStride)323 TEST_F(ResolverStorageClassLayoutValidationTest,
324 UniformBuffer_InvalidArrayStride) {
325 // type Inner = [[stride(8)]] array<f32, 10>;
326 //
327 // [[block]]
328 // struct Outer {
329 // inner : Inner;
330 // scalar : i32;
331 // };
332 //
333 // [[group(0), binding(0)]]
334 // var<uniform> a : Outer;
335
336 Alias("Inner", ty.array(ty.f32(), 10, 8));
337
338 Structure(Source{{12, 34}}, "Outer",
339 {
340 Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
341 Member("scalar", ty.i32()),
342 },
343 {StructBlock()});
344
345 Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
346 ast::StorageClass::kUniform, GroupAndBinding(0, 0));
347
348 ASSERT_FALSE(r()->Resolve());
349 EXPECT_EQ(
350 r()->error(),
351 R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array stride of 'inner' is currently 8. Consider setting [[stride(16)]] on the array type
352 12:34 note: see layout of struct:
353 /* align(4) size(84) */ struct Outer {
354 /* offset( 0) align(4) size(80) */ inner : [[stride(8)]] array<f32, 10>;
355 /* offset(80) align(4) size( 4) */ scalar : i32;
356 /* */ };
357 78:90 note: see declaration of variable)");
358 }
359
TEST_F(ResolverStorageClassLayoutValidationTest,UniformBuffer_InvalidArrayStride_SuggestedFix)360 TEST_F(ResolverStorageClassLayoutValidationTest,
361 UniformBuffer_InvalidArrayStride_SuggestedFix) {
362 // type Inner = [[stride(16)]] array<f32, 10>;
363 //
364 // [[block]]
365 // struct Outer {
366 // inner : Inner;
367 // scalar : i32;
368 // };
369 //
370 // [[group(0), binding(0)]]
371 // var<uniform> a : Outer;
372
373 Alias("Inner", ty.array(ty.f32(), 10, 16));
374
375 Structure(Source{{12, 34}}, "Outer",
376 {
377 Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
378 Member("scalar", ty.i32()),
379 },
380 {StructBlock()});
381
382 Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
383 ast::StorageClass::kUniform, GroupAndBinding(0, 0));
384
385 ASSERT_TRUE(r()->Resolve()) << r()->error();
386 }
387
388 } // namespace
389 } // namespace resolver
390 } // namespace tint
391