SkSLFinalizationChecks.cpp (9108B)
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: 50 FinalizationVisitor(const Context& c, const ProgramUsage& u) : fContext(c), fUsage(u) {} 51 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(skbug.com/40044753): 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 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 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(skbug.com/40044753): 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 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 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 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 186 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 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