xref: /aosp_15_r20/external/skia/src/sksl/analysis/SkSLFinalizationChecks.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2022 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 "include/core/SkSpan.h"
9 #include "include/core/SkTypes.h"
10 #include "src/base/SkEnumBitMask.h"
11 #include "src/base/SkSafeMath.h"
12 #include "src/core/SkTHash.h"
13 #include "src/sksl/SkSLAnalysis.h"
14 #include "src/sksl/SkSLBuiltinTypes.h"
15 #include "src/sksl/SkSLContext.h"
16 #include "src/sksl/SkSLDefines.h"
17 #include "src/sksl/SkSLErrorReporter.h"
18 #include "src/sksl/SkSLPosition.h"
19 #include "src/sksl/SkSLProgramSettings.h"
20 #include "src/sksl/analysis/SkSLProgramUsage.h"
21 #include "src/sksl/analysis/SkSLProgramVisitor.h"
22 #include "src/sksl/ir/SkSLExpression.h"
23 #include "src/sksl/ir/SkSLFunctionCall.h"
24 #include "src/sksl/ir/SkSLFunctionDeclaration.h"
25 #include "src/sksl/ir/SkSLFunctionDefinition.h"
26 #include "src/sksl/ir/SkSLIRNode.h"
27 #include "src/sksl/ir/SkSLInterfaceBlock.h"
28 #include "src/sksl/ir/SkSLLayout.h"
29 #include "src/sksl/ir/SkSLModifierFlags.h"
30 #include "src/sksl/ir/SkSLModifiersDeclaration.h"
31 #include "src/sksl/ir/SkSLProgram.h"
32 #include "src/sksl/ir/SkSLProgramElement.h"
33 #include "src/sksl/ir/SkSLType.h"
34 #include "src/sksl/ir/SkSLVarDeclarations.h"
35 #include "src/sksl/ir/SkSLVariable.h"
36 
37 #include <cstddef>
38 #include <cstdint>
39 #include <memory>
40 #include <string>
41 #include <vector>
42 
43 using namespace skia_private;
44 
45 namespace SkSL {
46 namespace {
47 
48 class FinalizationVisitor : public ProgramVisitor {
49 public:
FinalizationVisitor(const Context & c,const ProgramUsage & u)50     FinalizationVisitor(const Context& c, const ProgramUsage& u) : fContext(c), fUsage(u) {}
51 
visitProgramElement(const ProgramElement & pe)52     bool visitProgramElement(const ProgramElement& pe) override {
53         switch (pe.kind()) {
54             case ProgramElement::Kind::kGlobalVar:
55                 this->checkGlobalVariableSizeLimit(pe.as<GlobalVarDeclaration>());
56                 break;
57             case ProgramElement::Kind::kInterfaceBlock:
58                 // TODO(skia:13664): Enforce duplicate checks universally. This is currently not
59                 // possible without changes to the binding index assignment logic in graphite.
60                 this->checkBindUniqueness(pe.as<InterfaceBlock>());
61                 break;
62             case ProgramElement::Kind::kFunction:
63                 this->checkOutParamsAreAssigned(pe.as<FunctionDefinition>());
64                 break;
65             case ProgramElement::Kind::kModifiers:
66                 this->checkWorkgroupLocalSize(pe.as<ModifiersDeclaration>());
67                 break;
68             default:
69                 break;
70         }
71         return INHERITED::visitProgramElement(pe);
72     }
73 
checkGlobalVariableSizeLimit(const GlobalVarDeclaration & globalDecl)74     void checkGlobalVariableSizeLimit(const GlobalVarDeclaration& globalDecl) {
75         if (!ProgramConfig::IsRuntimeEffect(fContext.fConfig->fKind)) {
76             return;
77         }
78         const VarDeclaration& decl = globalDecl.varDeclaration();
79 
80         size_t prevSlotsUsed = fGlobalSlotsUsed;
81         fGlobalSlotsUsed = SkSafeMath::Add(fGlobalSlotsUsed, decl.var()->type().slotCount());
82         // To avoid overzealous error reporting, only trigger the error at the first place where the
83         // global limit is exceeded.
84         if (prevSlotsUsed < kVariableSlotLimit && fGlobalSlotsUsed >= kVariableSlotLimit) {
85             fContext.fErrors->error(decl.fPosition,
86                                     "global variable '" + std::string(decl.var()->name()) +
87                                     "' exceeds the size limit");
88         }
89     }
90 
checkBindUniqueness(const InterfaceBlock & block)91     void checkBindUniqueness(const InterfaceBlock& block) {
92         const Variable* var = block.var();
93         int32_t set = var->layout().fSet;
94         int32_t binding = var->layout().fBinding;
95         if (binding != -1) {
96             // TODO(skia:13664): This should map a `set` value of -1 to the default settings value
97             // used by codegen backends to prevent duplicates that may arise from the effective
98             // default set value.
99             uint64_t key = ((uint64_t)set << 32) + binding;
100             if (!fBindings.contains(key)) {
101                 fBindings.add(key);
102             } else {
103                 if (set != -1) {
104                     fContext.fErrors->error(block.fPosition,
105                                             "layout(set=" + std::to_string(set) +
106                                                     ", binding=" + std::to_string(binding) +
107                                                     ") has already been defined");
108                 } else {
109                     fContext.fErrors->error(block.fPosition,
110                                             "layout(binding=" + std::to_string(binding) +
111                                                     ") has already been defined");
112                 }
113             }
114         }
115     }
116 
checkOutParamsAreAssigned(const FunctionDefinition & funcDef)117     void checkOutParamsAreAssigned(const FunctionDefinition& funcDef) {
118         const FunctionDeclaration& funcDecl = funcDef.declaration();
119 
120         // Searches for `out` parameters that are not written to. According to the GLSL spec,
121         // the value of an out-param that's never assigned to is unspecified, so report it.
122         for (const Variable* param : funcDecl.parameters()) {
123             const ModifierFlags paramInout = param->modifierFlags() & (ModifierFlag::kIn |
124                                                                        ModifierFlag::kOut);
125             if (paramInout == ModifierFlag::kOut) {
126                 ProgramUsage::VariableCounts counts = fUsage.get(*param);
127                 if (counts.fWrite <= 0) {
128                     fContext.fErrors->error(param->fPosition,
129                                             "function '" + std::string(funcDecl.name()) +
130                                             "' never assigns a value to out parameter '" +
131                                             std::string(param->name()) + "'");
132                 }
133             }
134         }
135     }
136 
checkWorkgroupLocalSize(const ModifiersDeclaration & d)137     void checkWorkgroupLocalSize(const ModifiersDeclaration& d) {
138         if (d.layout().fLocalSizeX >= 0) {
139             if (fLocalSizeX >= 0) {
140                 fContext.fErrors->error(d.fPosition, "'local_size_x' was specified more than once");
141             } else {
142                 fLocalSizeX = d.layout().fLocalSizeX;
143             }
144         }
145         if (d.layout().fLocalSizeY >= 0) {
146             if (fLocalSizeY >= 0) {
147                 fContext.fErrors->error(d.fPosition, "'local_size_y' was specified more than once");
148             } else {
149                 fLocalSizeY = d.layout().fLocalSizeY;
150             }
151         }
152         if (d.layout().fLocalSizeZ >= 0) {
153             if (fLocalSizeZ >= 0) {
154                 fContext.fErrors->error(d.fPosition, "'local_size_z' was specified more than once");
155             } else {
156                 fLocalSizeZ = d.layout().fLocalSizeZ;
157             }
158         }
159     }
160 
visitExpression(const Expression & expr)161     bool visitExpression(const Expression& expr) override {
162         switch (expr.kind()) {
163             case Expression::Kind::kFunctionCall: {
164                 const FunctionDeclaration& decl = expr.as<FunctionCall>().function();
165                 if (!decl.isBuiltin() && !decl.definition()) {
166                     fContext.fErrors->error(expr.fPosition, "function '" + decl.description() +
167                                                             "' is not defined");
168                 }
169                 break;
170             }
171             case Expression::Kind::kFunctionReference:
172             case Expression::Kind::kMethodReference:
173             case Expression::Kind::kTypeReference:
174                 SkDEBUGFAIL("invalid reference-expr, should have been reported by coerce()");
175                 fContext.fErrors->error(expr.fPosition, "invalid expression");
176                 break;
177             default:
178                 if (expr.type().matches(*fContext.fTypes.fInvalid)) {
179                     fContext.fErrors->error(expr.fPosition, "invalid expression");
180                 }
181                 break;
182         }
183         return INHERITED::visitExpression(expr);
184     }
185 
definesLocalSize() const186     bool definesLocalSize() const {
187         return fLocalSizeX >= 0 || fLocalSizeY >= 0 || fLocalSizeZ >= 0;
188     }
189 
190 private:
191     using INHERITED = ProgramVisitor;
192     size_t fGlobalSlotsUsed = 0;
193     const Context& fContext;
194     const ProgramUsage& fUsage;
195     // we pack the set/binding pair into a single 64 bit int
196     THashSet<uint64_t> fBindings;
197 
198     // Compute programs must at least specify the X dimension of the local size. The other
199     // dimensions have a default value of "1".
200     int fLocalSizeX = -1;
201     int fLocalSizeY = -1;
202     int fLocalSizeZ = -1;
203 };
204 
205 }  // namespace
206 
DoFinalizationChecks(const Program & program)207 void Analysis::DoFinalizationChecks(const Program& program) {
208     // Check all of the program's owned elements. (Built-in elements are assumed to be valid.)
209     FinalizationVisitor visitor{*program.fContext, *program.usage()};
210     for (const std::unique_ptr<ProgramElement>& element : program.fOwnedElements) {
211         visitor.visitProgramElement(*element);
212     }
213     if (ProgramConfig::IsCompute(program.fConfig->fKind) && !visitor.definesLocalSize()) {
214         program.fContext->fErrors->error(Position(),
215                                          "compute programs must specify a workgroup size");
216     }
217 }
218 
219 }  // namespace SkSL
220