| Index: src/sksl/SkSLCompiler.cpp
|
| diff --git a/src/sksl/SkSLCompiler.cpp b/src/sksl/SkSLCompiler.cpp
|
| index 9eeacd082de1870f76135a21b9d88d91c0f25a0b..d4fbc95d395d75a3ab0ee78e6a260314e5544c2a 100644
|
| --- a/src/sksl/SkSLCompiler.cpp
|
| +++ b/src/sksl/SkSLCompiler.cpp
|
| @@ -11,6 +11,7 @@
|
| #include <streambuf>
|
|
|
| #include "ast/SkSLASTPrecision.h"
|
| +#include "SkSLCFGGenerator.h"
|
| #include "SkSLIRGenerator.h"
|
| #include "SkSLParser.h"
|
| #include "SkSLSPIRVCodeGenerator.h"
|
| @@ -18,7 +19,7 @@
|
| #include "ir/SkSLIntLiteral.h"
|
| #include "ir/SkSLModifiersDeclaration.h"
|
| #include "ir/SkSLSymbolTable.h"
|
| -#include "ir/SkSLVarDeclaration.h"
|
| +#include "ir/SkSLVarDeclarations.h"
|
| #include "SkMutex.h"
|
|
|
| #define STRINGIFY(x) #x
|
| @@ -141,6 +142,180 @@ Compiler::~Compiler() {
|
| delete fIRGenerator;
|
| }
|
|
|
| +// add the definition created by assigning to the lvalue to the definition set
|
| +void Compiler::addDefinition(const Expression* lvalue, const Expression* expr,
|
| + std::unordered_map<const Variable*, const Expression*>* definitions) {
|
| + switch (lvalue->fKind) {
|
| + case Expression::kVariableReference_Kind: {
|
| + const Variable& var = ((VariableReference*) lvalue)->fVariable;
|
| + if (var.fStorage == Variable::kLocal_Storage) {
|
| + (*definitions)[&var] = expr;
|
| + }
|
| + break;
|
| + }
|
| + case Expression::kSwizzle_Kind:
|
| + // We consider the variable written to as long as at least some of its components have
|
| + // been written to. This will lead to some false negatives (we won't catch it if you
|
| + // write to foo.x and then read foo.y), but being stricter could lead to false positives
|
| + // (we write to foo.x, and then pass foo to a function which happens to only read foo.x,
|
| + // but since we pass foo as a whole it is flagged as an error) unless we perform a much
|
| + // more complicated whole-program analysis. This is probably good enough.
|
| + this->addDefinition(((Swizzle*) lvalue)->fBase.get(),
|
| + fContext.fDefined_Expression.get(),
|
| + definitions);
|
| + break;
|
| + case Expression::kIndex_Kind:
|
| + // see comments in Swizzle
|
| + this->addDefinition(((IndexExpression*) lvalue)->fBase.get(),
|
| + fContext.fDefined_Expression.get(),
|
| + definitions);
|
| + break;
|
| + case Expression::kFieldAccess_Kind:
|
| + // see comments in Swizzle
|
| + this->addDefinition(((FieldAccess*) lvalue)->fBase.get(),
|
| + fContext.fDefined_Expression.get(),
|
| + definitions);
|
| + break;
|
| + default:
|
| + // not an lvalue, can't happen
|
| + ASSERT(false);
|
| + }
|
| +}
|
| +
|
| +// add local variables defined by this node to the set
|
| +void Compiler::addDefinitions(const BasicBlock::Node& node,
|
| + std::unordered_map<const Variable*, const Expression*>* definitions) {
|
| + switch (node.fKind) {
|
| + case BasicBlock::Node::kExpression_Kind: {
|
| + const Expression* expr = (Expression*) node.fNode;
|
| + if (expr->fKind == Expression::kBinary_Kind) {
|
| + const BinaryExpression* b = (BinaryExpression*) expr;
|
| + if (b->fOperator == Token::EQ) {
|
| + this->addDefinition(b->fLeft.get(), b->fRight.get(), definitions);
|
| + }
|
| + }
|
| + break;
|
| + }
|
| + case BasicBlock::Node::kStatement_Kind: {
|
| + const Statement* stmt = (Statement*) node.fNode;
|
| + if (stmt->fKind == Statement::kVarDeclarations_Kind) {
|
| + const VarDeclarationsStatement* vd = (VarDeclarationsStatement*) stmt;
|
| + for (const VarDeclaration& decl : vd->fDeclaration->fVars) {
|
| + if (decl.fValue) {
|
| + (*definitions)[decl.fVar] = decl.fValue.get();
|
| + }
|
| + }
|
| + }
|
| + break;
|
| + }
|
| + }
|
| +}
|
| +
|
| +void Compiler::scanCFG(CFG* cfg, BlockId blockId, std::set<BlockId>* workList) {
|
| + BasicBlock& block = cfg->fBlocks[blockId];
|
| +
|
| + // compute definitions after this block
|
| + std::unordered_map<const Variable*, const Expression*> after = block.fBefore;
|
| + for (const BasicBlock::Node& n : block.fNodes) {
|
| + this->addDefinitions(n, &after);
|
| + }
|
| +
|
| + // propagate definitions to exits
|
| + for (BlockId exitId : block.fExits) {
|
| + BasicBlock& exit = cfg->fBlocks[exitId];
|
| + for (const auto& pair : after) {
|
| + const Expression* e1 = pair.second;
|
| + if (exit.fBefore.find(pair.first) == exit.fBefore.end()) {
|
| + exit.fBefore[pair.first] = e1;
|
| + } else {
|
| + const Expression* e2 = exit.fBefore[pair.first];
|
| + if (e1 != e2) {
|
| + // definition has changed, merge and add exit block to worklist
|
| + workList->insert(exitId);
|
| + if (!e1 || !e2) {
|
| + exit.fBefore[pair.first] = nullptr;
|
| + } else {
|
| + exit.fBefore[pair.first] = fContext.fDefined_Expression.get();
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +// returns a map which maps all local variables in the function to null, indicating that their value
|
| +// is initially unknown
|
| +static std::unordered_map<const Variable*, const Expression*> compute_start_state(const CFG& cfg) {
|
| + std::unordered_map<const Variable*, const Expression*> result;
|
| + for (const auto block : cfg.fBlocks) {
|
| + for (const auto node : block.fNodes) {
|
| + if (node.fKind == BasicBlock::Node::kStatement_Kind) {
|
| + const Statement* s = (Statement*) node.fNode;
|
| + if (s->fKind == Statement::kVarDeclarations_Kind) {
|
| + const VarDeclarationsStatement* vd = (const VarDeclarationsStatement*) s;
|
| + for (const VarDeclaration& decl : vd->fDeclaration->fVars) {
|
| + result[decl.fVar] = nullptr;
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| + return result;
|
| +}
|
| +
|
| +void Compiler::scanCFG(const FunctionDefinition& f) {
|
| + CFG cfg = CFGGenerator().getCFG(f);
|
| +
|
| + // compute the data flow
|
| + cfg.fBlocks[cfg.fStart].fBefore = compute_start_state(cfg);
|
| + std::set<BlockId> workList;
|
| + for (BlockId i = 0; i < cfg.fBlocks.size(); i++) {
|
| + workList.insert(i);
|
| + }
|
| + while (workList.size()) {
|
| + BlockId next = *workList.begin();
|
| + workList.erase(workList.begin());
|
| + this->scanCFG(&cfg, next, &workList);
|
| + }
|
| +
|
| + // check for unreachable code
|
| + for (size_t i = 0; i < cfg.fBlocks.size(); i++) {
|
| + if (i != cfg.fStart && !cfg.fBlocks[i].fEntrances.size() &&
|
| + cfg.fBlocks[i].fNodes.size()) {
|
| + this->error(cfg.fBlocks[i].fNodes[0].fNode->fPosition, "unreachable");
|
| + }
|
| + }
|
| + if (fErrorCount) {
|
| + return;
|
| + }
|
| +
|
| + // check for undefined variables
|
| + for (const BasicBlock& b : cfg.fBlocks) {
|
| + std::unordered_map<const Variable*, const Expression*> definitions = b.fBefore;
|
| + for (const BasicBlock::Node& n : b.fNodes) {
|
| + if (n.fKind == BasicBlock::Node::kExpression_Kind) {
|
| + const Expression* expr = (const Expression*) n.fNode;
|
| + if (expr->fKind == Expression::kVariableReference_Kind) {
|
| + const Variable& var = ((VariableReference*) expr)->fVariable;
|
| + if (var.fStorage == Variable::kLocal_Storage &&
|
| + !definitions[&var]) {
|
| + this->error(expr->fPosition,
|
| + "'" + var.fName + "' has not been assigned");
|
| + }
|
| + }
|
| + }
|
| + this->addDefinitions(n, &definitions);
|
| + }
|
| + }
|
| +
|
| + // check for missing return
|
| + if (f.fDeclaration.fReturnType != *fContext.fVoid_Type) {
|
| + if (cfg.fBlocks[cfg.fExit].fEntrances.size()) {
|
| + this->error(f.fPosition, "function can exit without returning a value");
|
| + }
|
| + }
|
| +}
|
| +
|
| void Compiler::internalConvertProgram(std::string text,
|
| Modifiers::Flag* defaultPrecision,
|
| std::vector<std::unique_ptr<ProgramElement>>* result) {
|
| @@ -165,7 +340,8 @@ void Compiler::internalConvertProgram(std::string text,
|
| case ASTDeclaration::kFunction_Kind: {
|
| std::unique_ptr<FunctionDefinition> f = fIRGenerator->convertFunction(
|
| (ASTFunction&) decl);
|
| - if (f) {
|
| + if (!fErrorCount && f) {
|
| + this->scanCFG(*f);
|
| result->push_back(std::move(f));
|
| }
|
| break;
|
|
|