• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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