/* * Copyright 2016 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef SKSL_CFGGENERATOR #define SKSL_CFGGENERATOR #include "ir/SkSLExpression.h" #include "ir/SkSLFunctionDefinition.h" #include #include namespace SkSL { // index of a block within CFG.fBlocks typedef size_t BlockId; struct BasicBlock { struct Node { enum Kind { kStatement_Kind, kExpression_Kind }; Node(Kind kind, bool constantPropagation, std::unique_ptr* expression, std::unique_ptr* statement) : fKind(kind) , fConstantPropagation(constantPropagation) , fExpression(expression) , fStatement(statement) {} std::unique_ptr* expression() const { SkASSERT(fKind == kExpression_Kind); return fExpression; } void setExpression(std::unique_ptr expr) { SkASSERT(fKind == kExpression_Kind); *fExpression = std::move(expr); } std::unique_ptr* statement() const { SkASSERT(fKind == kStatement_Kind); return fStatement; } void setStatement(std::unique_ptr stmt) { SkASSERT(fKind == kStatement_Kind); *fStatement = std::move(stmt); } String description() const { if (fKind == kStatement_Kind) { return (*fStatement)->description(); } else { SkASSERT(fKind == kExpression_Kind); return (*fExpression)->description(); } } Kind fKind; // if false, this node should not be subject to constant propagation. This happens with // compound assignment (i.e. x *= 2), in which the value x is used as an rvalue for // multiplication by 2 and then as an lvalue for assignment purposes. Since there is only // one "x" node, replacing it with a constant would break the assignment and we suppress // it. Down the road, we should handle this more elegantly by substituting a regular // assignment if the target is constant (i.e. x = 1; x *= 2; should become x = 1; x = 1 * 2; // and then collapse down to a simple x = 2;). bool fConstantPropagation; private: // we store pointers to the unique_ptrs so that we can replace expressions or statements // during optimization without having to regenerate the entire tree std::unique_ptr* fExpression; std::unique_ptr* fStatement; }; /** * Attempts to remove the expression (and its subexpressions) pointed to by the iterator. If the * expression can be cleanly removed, returns true and updates the iterator to point to the * expression after the deleted expression. Otherwise returns false (and the CFG will need to be * regenerated). */ bool tryRemoveExpression(std::vector::iterator* iter); /** * Locates and attempts remove an expression occurring before the expression pointed to by iter. * If the expression can be cleanly removed, returns true and resets iter to a valid iterator * pointing to the same expression it did initially. Otherwise returns false (and the CFG will * need to be regenerated). */ bool tryRemoveExpressionBefore(std::vector::iterator* iter, Expression* e); /** * As tryRemoveExpressionBefore, but for lvalues. As lvalues are at most partially evaluated * (for instance, x[i] = 0 evaluates i but not x) this will only look for the parts of the * lvalue that are actually evaluated. */ bool tryRemoveLValueBefore(std::vector::iterator* iter, Expression* lvalue); /** * Attempts to inserts a new expression before the node pointed to by iter. If the * expression can be cleanly inserted, returns true and updates the iterator to point to the * newly inserted expression. Otherwise returns false (and the CFG will need to be regenerated). */ bool tryInsertExpression(std::vector::iterator* iter, std::unique_ptr* expr); std::vector fNodes; std::set fEntrances; std::set fExits; // variable definitions upon entering this basic block (null expression = undefined) DefinitionMap fBefore; }; struct CFG { BlockId fStart; BlockId fExit; std::vector fBlocks; void dump(); private: BlockId fCurrent; // Adds a new block, adds an exit* from the current block to the new block, then marks the new // block as the current block // *see note in addExit() BlockId newBlock(); // Adds a new block, but does not mark it current or add an exit from the current block BlockId newIsolatedBlock(); // Adds an exit from the 'from' block to the 'to' block // Note that we skip adding the exit if the 'from' block is itself unreachable; this means that // we don't actually have to trace the tree to see if a particular block is unreachable, we can // just check to see if it has any entrances. This does require a bit of care in the order in // which we set the CFG up. void addExit(BlockId from, BlockId to); friend class CFGGenerator; }; /** * Converts functions into control flow graphs. */ class CFGGenerator { public: CFGGenerator() {} CFG getCFG(FunctionDefinition& f); private: void addStatement(CFG& cfg, std::unique_ptr* s); void addExpression(CFG& cfg, std::unique_ptr* e, bool constantPropagate); void addLValue(CFG& cfg, std::unique_ptr* e); std::stack fLoopContinues; std::stack fLoopExits; }; } #endif