1 /*
2 * Copyright 2021 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "src/sksl/ir/SkSLVarDeclarations.h"
9
10 #include "include/core/SkSpan.h"
11 #include "include/private/base/SkTo.h"
12 #include "src/base/SkEnumBitMask.h"
13 #include "src/sksl/SkSLAnalysis.h"
14 #include "src/sksl/SkSLBuiltinTypes.h"
15 #include "src/sksl/SkSLCompiler.h"
16 #include "src/sksl/SkSLContext.h"
17 #include "src/sksl/SkSLErrorReporter.h"
18 #include "src/sksl/SkSLPosition.h"
19 #include "src/sksl/SkSLProgramKind.h"
20 #include "src/sksl/SkSLProgramSettings.h"
21 #include "src/sksl/SkSLString.h"
22 #include "src/sksl/ir/SkSLLayout.h"
23 #include "src/sksl/ir/SkSLModifierFlags.h"
24 #include "src/sksl/ir/SkSLModifiers.h"
25 #include "src/sksl/ir/SkSLSymbolTable.h"
26 #include "src/sksl/ir/SkSLType.h"
27
28 #include <string_view>
29
30 namespace SkSL {
31 namespace {
32
check_valid_uniform_type(Position pos,const Type * t,const Context & context,bool topLevel=true)33 static bool check_valid_uniform_type(Position pos,
34 const Type* t,
35 const Context& context,
36 bool topLevel = true) {
37 auto reportError = [&]() {
38 context.fErrors->error(pos, "variables of type '" + t->displayName() +
39 "' may not be uniform");
40 };
41
42 // In Runtime Effects we only allow a restricted set of types: shader, blender, colorFilter,
43 // 32-bit signed integers, 16-bit and 32-bit floats, and their vector/square-matrix composites.
44 if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
45 // `shader`, `blender`, `colorFilter`
46 if (t->isEffectChild()) {
47 return true;
48 }
49
50 // `int`, `int2`, `int3`, `int4`
51 const Type& ct = t->componentType();
52 if (ct.isSigned() && ct.bitWidth() == 32 && (t->isScalar() || t->isVector())) {
53 return true;
54 }
55
56 // `float`, `float2`, `float3`, `float4`, `float2x2`, `float3x3`, `float4x4`
57 // `half`, `half2`, `half3`, `half4`, `half2x2`, `half3x3`, `half4x4`
58 if (ct.isFloat() &&
59 (t->isScalar() || t->isVector() || (t->isMatrix() && t->rows() == t->columns()))) {
60 return true;
61 }
62
63 // Everything else is an error.
64 reportError();
65 return false;
66 }
67
68 Position errorPosition = {};
69 if (!t->isAllowedInUniform(&errorPosition)) {
70 reportError();
71 if (errorPosition.valid()) {
72 context.fErrors->error(errorPosition, "caused by:");
73 }
74 return false;
75 }
76
77 return true;
78 }
79
80 } // namespace
81
description() const82 std::string VarDeclaration::description() const {
83 std::string result = this->var()->layout().paddedDescription() +
84 this->var()->modifierFlags().paddedDescription() +
85 this->baseType().description() + ' ' + std::string(this->var()->name());
86 if (this->arraySize() > 0) {
87 String::appendf(&result, "[%d]", this->arraySize());
88 }
89 if (this->value()) {
90 result += " = " + this->value()->description();
91 }
92 result += ";";
93 return result;
94 }
95
ErrorCheck(const Context & context,Position pos,Position modifiersPosition,const Layout & layout,ModifierFlags modifierFlags,const Type * type,const Type * baseType,Variable::Storage storage)96 void VarDeclaration::ErrorCheck(const Context& context,
97 Position pos,
98 Position modifiersPosition,
99 const Layout& layout,
100 ModifierFlags modifierFlags,
101 const Type* type,
102 const Type* baseType,
103 Variable::Storage storage) {
104 SkASSERT(type->isArray() ? baseType->matches(type->componentType())
105 : baseType->matches(*type));
106
107 if (baseType->componentType().isOpaque() && !baseType->componentType().isAtomic() &&
108 storage != Variable::Storage::kGlobal) {
109 context.fErrors->error(pos, "variables of type '" + baseType->displayName() +
110 "' must be global");
111 }
112 if ((modifierFlags & ModifierFlag::kIn) && baseType->isMatrix()) {
113 context.fErrors->error(pos, "'in' variables may not have matrix type");
114 }
115 if ((modifierFlags & ModifierFlag::kIn) && type->isUnsizedArray()) {
116 context.fErrors->error(pos, "'in' variables may not have unsized array type");
117 }
118 if ((modifierFlags & ModifierFlag::kOut) && type->isUnsizedArray()) {
119 context.fErrors->error(pos, "'out' variables may not have unsized array type");
120 }
121 if ((modifierFlags & ModifierFlag::kIn) && modifierFlags.isUniform()) {
122 context.fErrors->error(pos, "'in uniform' variables not permitted");
123 }
124 if (modifierFlags.isReadOnly() && modifierFlags.isWriteOnly()) {
125 context.fErrors->error(pos, "'readonly' and 'writeonly' qualifiers cannot be combined");
126 }
127 if (modifierFlags.isUniform() && modifierFlags.isBuffer()) {
128 context.fErrors->error(pos, "'uniform buffer' variables not permitted");
129 }
130 if (modifierFlags.isWorkgroup() && (modifierFlags & (ModifierFlag::kIn |
131 ModifierFlag::kOut))) {
132 context.fErrors->error(pos, "in / out variables may not be declared workgroup");
133 }
134 if (modifierFlags.isUniform()) {
135 check_valid_uniform_type(pos, baseType, context);
136 }
137 if (baseType->isEffectChild() && !modifierFlags.isUniform()) {
138 context.fErrors->error(pos, "variables of type '" + baseType->displayName() +
139 "' must be uniform");
140 }
141 if (baseType->isEffectChild() && context.fConfig->fKind == ProgramKind::kMeshVertex) {
142 context.fErrors->error(pos, "effects are not permitted in mesh vertex shaders");
143 }
144 if (baseType->isOrContainsAtomic()) {
145 // An atomic variable (or a struct or an array that contains an atomic member) must be
146 // either:
147 // a. Declared as a workgroup-shared variable, OR
148 // b. Declared as the member of writable storage buffer block (i.e. has no readonly
149 // restriction).
150 //
151 // The checks below will enforce these two rules on all declarations. If the variable is not
152 // declared with the workgroup modifier, then it must be declared in the interface block
153 // storage. If this is the declaration for an interface block that contains an atomic
154 // member, then it must have the `buffer` modifier and no `readonly` modifier.
155 bool isBlockMember = (storage == Variable::Storage::kInterfaceBlock);
156 bool isWritableStorageBuffer = modifierFlags.isBuffer() && !modifierFlags.isReadOnly();
157
158 if (!modifierFlags.isWorkgroup() &&
159 !(baseType->isInterfaceBlock() ? isWritableStorageBuffer : isBlockMember)) {
160 context.fErrors->error(pos, "atomics are only permitted in workgroup variables and "
161 "writable storage blocks");
162 }
163 }
164 if (layout.fFlags & LayoutFlag::kColor) {
165 if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
166 context.fErrors->error(pos, "'layout(color)' is only permitted in runtime effects");
167 }
168 if (!modifierFlags.isUniform()) {
169 context.fErrors->error(pos, "'layout(color)' is only permitted on 'uniform' variables");
170 }
171 auto validColorXformType = [](const Type& t) {
172 return t.isVector() && t.componentType().isFloat() &&
173 (t.columns() == 3 || t.columns() == 4);
174 };
175 if (!validColorXformType(*baseType)) {
176 context.fErrors->error(pos, "'layout(color)' is not permitted on variables of type '" +
177 baseType->displayName() + "'");
178 }
179 }
180
181 ModifierFlags permitted = ModifierFlag::kConst | ModifierFlag::kHighp | ModifierFlag::kMediump |
182 ModifierFlag::kLowp;
183 if (storage == Variable::Storage::kGlobal) {
184 // Uniforms are allowed in all programs
185 permitted |= ModifierFlag::kUniform;
186
187 // No other modifiers are allowed in runtime effects.
188 if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
189 if (baseType->isInterfaceBlock()) {
190 // Interface blocks allow `buffer`.
191 permitted |= ModifierFlag::kBuffer;
192
193 if (modifierFlags.isBuffer()) {
194 // Only storage blocks allow `readonly` and `writeonly`.
195 // (`readonly` and `writeonly` textures are converted to separate types via
196 // applyAccessQualifiers.)
197 permitted |= ModifierFlag::kReadOnly | ModifierFlag::kWriteOnly;
198 }
199
200 // It is an error for an unsized array to appear anywhere but the last member of a
201 // "buffer" block.
202 const auto& fields = baseType->fields();
203 const int illegalRangeEnd = SkToInt(fields.size()) -
204 (modifierFlags.isBuffer() ? 1 : 0);
205 for (int i = 0; i < illegalRangeEnd; ++i) {
206 if (fields[i].fType->isUnsizedArray()) {
207 context.fErrors->error(
208 fields[i].fPosition,
209 "unsized array must be the last member of a storage block");
210 }
211 }
212 }
213
214 if (!baseType->isOpaque()) {
215 // Only non-opaque types allow `in` and `out`.
216 permitted |= ModifierFlag::kIn | ModifierFlag::kOut;
217 }
218 if (ProgramConfig::IsFragment(context.fConfig->fKind) && baseType->isStruct() &&
219 !baseType->isInterfaceBlock()) {
220 // Only structs in fragment shaders allow `pixel_local`.
221 permitted |= ModifierFlag::kPixelLocal;
222 }
223 if (ProgramConfig::IsCompute(context.fConfig->fKind)) {
224 // Only compute shaders allow `workgroup`.
225 if (!baseType->isOpaque() || baseType->isAtomic()) {
226 permitted |= ModifierFlag::kWorkgroup;
227 }
228 } else {
229 // Only vertex/fragment shaders allow `flat` and `noperspective`.
230 permitted |= ModifierFlag::kFlat | ModifierFlag::kNoPerspective;
231 }
232 }
233 }
234
235 LayoutFlags permittedLayoutFlags = LayoutFlag::kAll;
236
237 // Pixel format modifiers are required on storage textures, and forbidden on other types.
238 if (baseType->isStorageTexture()) {
239 if (!(layout.fFlags & LayoutFlag::kAllPixelFormats)) {
240 context.fErrors->error(pos, "storage textures must declare a pixel format");
241 }
242 } else {
243 permittedLayoutFlags &= ~LayoutFlag::kAllPixelFormats;
244 }
245
246 // The `texture` and `sampler` modifiers can be present respectively on a texture and sampler or
247 // simultaneously on a combined image-sampler but they are not permitted on any other type.
248 switch (baseType->typeKind()) {
249 case Type::TypeKind::kSampler:
250 // Both texture and sampler flags are permitted
251 break;
252 case Type::TypeKind::kTexture:
253 permittedLayoutFlags &= ~LayoutFlag::kSampler;
254 break;
255 case Type::TypeKind::kSeparateSampler:
256 permittedLayoutFlags &= ~LayoutFlag::kTexture;
257 break;
258 default:
259 permittedLayoutFlags &= ~(LayoutFlag::kTexture | LayoutFlag::kSampler);
260 break;
261 }
262
263 // We don't allow 'binding' or 'set' on normal uniform variables, only on textures, samplers,
264 // and interface blocks (holding uniform variables). They're also only allowed at global scope,
265 // not on interface block fields (or locals/parameters).
266 bool permitBindingAndSet = baseType->typeKind() == Type::TypeKind::kSampler ||
267 baseType->typeKind() == Type::TypeKind::kSeparateSampler ||
268 baseType->typeKind() == Type::TypeKind::kTexture ||
269 baseType->isInterfaceBlock();
270 if (storage != Variable::Storage::kGlobal || (modifierFlags.isUniform() &&
271 !permitBindingAndSet)) {
272 permittedLayoutFlags &= ~LayoutFlag::kBinding;
273 permittedLayoutFlags &= ~LayoutFlag::kSet;
274 permittedLayoutFlags &= ~LayoutFlag::kAllBackends;
275 }
276 if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
277 // Disallow all layout flags except 'color' in runtime effects
278 permittedLayoutFlags &= LayoutFlag::kColor;
279 }
280
281 // The `push_constant` flag isn't allowed on in-variables, out-variables, bindings or sets.
282 if ((layout.fFlags & (LayoutFlag::kSet | LayoutFlag::kBinding)) ||
283 (modifierFlags & (ModifierFlag::kIn | ModifierFlag::kOut))) {
284 permittedLayoutFlags &= ~LayoutFlag::kPushConstant;
285 }
286 // The `builtin` layout flag is only allowed in modules.
287 if (!context.fConfig->isBuiltinCode()) {
288 permittedLayoutFlags &= ~LayoutFlag::kBuiltin;
289 }
290
291 modifierFlags.checkPermittedFlags(context, modifiersPosition, permitted);
292 layout.checkPermittedLayout(context, modifiersPosition, permittedLayoutFlags);
293 }
294
ErrorCheckAndCoerce(const Context & context,const Variable & var,const Type * baseType,std::unique_ptr<Expression> & value)295 bool VarDeclaration::ErrorCheckAndCoerce(const Context& context,
296 const Variable& var,
297 const Type* baseType,
298 std::unique_ptr<Expression>& value) {
299 if (baseType->matches(*context.fTypes.fInvalid)) {
300 context.fErrors->error(var.fPosition, "invalid type");
301 return false;
302 }
303 if (baseType->isVoid()) {
304 context.fErrors->error(var.fPosition, "variables of type 'void' are not allowed");
305 return false;
306 }
307
308 ErrorCheck(context, var.fPosition, var.modifiersPosition(), var.layout(), var.modifierFlags(),
309 &var.type(), baseType, var.storage());
310 if (value) {
311 if (var.type().isOpaque() || var.type().isOrContainsAtomic()) {
312 context.fErrors->error(value->fPosition, "opaque type '" + var.type().displayName() +
313 "' cannot use initializer expressions");
314 return false;
315 }
316 if (var.modifierFlags() & ModifierFlag::kIn) {
317 context.fErrors->error(value->fPosition,
318 "'in' variables cannot use initializer expressions");
319 return false;
320 }
321 if (var.modifierFlags().isUniform()) {
322 context.fErrors->error(value->fPosition,
323 "'uniform' variables cannot use initializer expressions");
324 return false;
325 }
326 if (var.storage() == Variable::Storage::kInterfaceBlock) {
327 context.fErrors->error(value->fPosition,
328 "initializers are not permitted on interface block fields");
329 return false;
330 }
331 if (context.fConfig->strictES2Mode() && var.type().isOrContainsArray()) {
332 context.fErrors->error(value->fPosition, "initializers are not permitted on arrays "
333 "(or structs containing arrays)");
334 return false;
335 }
336 value = var.type().coerceExpression(std::move(value), context);
337 if (!value) {
338 return false;
339 }
340 }
341 if (var.modifierFlags().isConst()) {
342 if (!value) {
343 context.fErrors->error(var.fPosition, "'const' variables must be initialized");
344 return false;
345 }
346 if (!Analysis::IsConstantExpression(*value)) {
347 context.fErrors->error(value->fPosition,
348 "'const' variable initializer must be a constant expression");
349 return false;
350 }
351 }
352 if (var.storage() == Variable::Storage::kInterfaceBlock) {
353 if (var.type().isOpaque()) {
354 context.fErrors->error(var.fPosition, "opaque type '" + var.type().displayName() +
355 "' is not permitted in an interface block");
356 return false;
357 }
358 }
359 if (var.storage() == Variable::Storage::kGlobal) {
360 if (value && !Analysis::IsConstantExpression(*value)) {
361 context.fErrors->error(value->fPosition,
362 "global variable initializer must be a constant expression");
363 return false;
364 }
365 }
366 return true;
367 }
368
Convert(const Context & context,Position overallPos,const Modifiers & modifiers,const Type & type,Position namePos,std::string_view name,VariableStorage storage,std::unique_ptr<Expression> value)369 std::unique_ptr<VarDeclaration> VarDeclaration::Convert(const Context& context,
370 Position overallPos,
371 const Modifiers& modifiers,
372 const Type& type,
373 Position namePos,
374 std::string_view name,
375 VariableStorage storage,
376 std::unique_ptr<Expression> value) {
377 // Parameter declaration-statements do not exist in the grammar (unlike, say, K&R C).
378 SkASSERT(storage != VariableStorage::kParameter);
379
380 std::unique_ptr<Variable> var = Variable::Convert(context,
381 overallPos,
382 modifiers.fPosition,
383 modifiers.fLayout,
384 modifiers.fFlags,
385 &type,
386 namePos,
387 name,
388 storage);
389 if (!var) {
390 return nullptr;
391 }
392 return VarDeclaration::Convert(context, std::move(var), std::move(value));
393 }
394
Convert(const Context & context,std::unique_ptr<Variable> var,std::unique_ptr<Expression> value)395 std::unique_ptr<VarDeclaration> VarDeclaration::Convert(const Context& context,
396 std::unique_ptr<Variable> var,
397 std::unique_ptr<Expression> value) {
398 const Type* baseType = &var->type();
399 int arraySize = 0;
400 if (baseType->isArray()) {
401 arraySize = baseType->columns();
402 baseType = &baseType->componentType();
403 }
404 if (!ErrorCheckAndCoerce(context, *var, baseType, value)) {
405 return nullptr;
406 }
407 std::unique_ptr<VarDeclaration> varDecl = VarDeclaration::Make(context, var.get(), baseType,
408 arraySize, std::move(value));
409 if (!varDecl) {
410 return nullptr;
411 }
412
413 if (var->storage() == Variable::Storage::kGlobal ||
414 var->storage() == Variable::Storage::kInterfaceBlock) {
415 // Check if this globally-scoped variable name overlaps an existing symbol name.
416 if (context.fSymbolTable->find(var->name())) {
417 context.fErrors->error(var->fPosition,
418 "symbol '" + std::string(var->name()) + "' was already defined");
419 return nullptr;
420 }
421
422 // `sk_RTAdjust` is special, and makes the IR generator emit position-fixup expressions.
423 if (var->name() == Compiler::RTADJUST_NAME) {
424 if (!var->type().matches(*context.fTypes.fFloat4)) {
425 context.fErrors->error(var->fPosition, "sk_RTAdjust must have type 'float4'");
426 return nullptr;
427 }
428 }
429 }
430
431 context.fSymbolTable->add(context, std::move(var));
432 return varDecl;
433 }
434
Make(const Context & context,Variable * var,const Type * baseType,int arraySize,std::unique_ptr<Expression> value)435 std::unique_ptr<VarDeclaration> VarDeclaration::Make(const Context& context,
436 Variable* var,
437 const Type* baseType,
438 int arraySize,
439 std::unique_ptr<Expression> value) {
440 SkASSERT(!baseType->isArray());
441 // function parameters cannot have variable declarations
442 SkASSERT(var->storage() != Variable::Storage::kParameter);
443 // 'const' variables must be initialized
444 SkASSERT(!var->modifierFlags().isConst() || value);
445 // 'const' variable initializer must be a constant expression
446 SkASSERT(!var->modifierFlags().isConst() || Analysis::IsConstantExpression(*value));
447 // global variable initializer must be a constant expression
448 SkASSERT(!(value && var->storage() == Variable::Storage::kGlobal &&
449 !Analysis::IsConstantExpression(*value)));
450 // opaque type not permitted on an interface block
451 SkASSERT(!(var->storage() == Variable::Storage::kInterfaceBlock && var->type().isOpaque()));
452 // initializers are not permitted on interface block fields
453 SkASSERT(!(var->storage() == Variable::Storage::kInterfaceBlock && value));
454 // opaque type cannot use initializer expressions
455 SkASSERT(!(value && var->type().isOpaque()));
456 // 'in' variables cannot use initializer expressions
457 SkASSERT(!(value && (var->modifierFlags() & ModifierFlag::kIn)));
458 // 'uniform' variables cannot use initializer expressions
459 SkASSERT(!(value && var->modifierFlags().isUniform()));
460 // in strict-ES2 mode, is-or-contains-array types cannot use initializer expressions
461 SkASSERT(!(value && var->type().isOrContainsArray() && context.fConfig->strictES2Mode()));
462
463 auto result = std::make_unique<VarDeclaration>(var, baseType, arraySize, std::move(value));
464 var->setVarDeclaration(result.get());
465 return result;
466 }
467
468 } // namespace SkSL
469