Improved skslc optimizer, particularly around vectors.

BUG=skia:

Change-Id: Idb364d9198f2ff84aad1eb68e236fb45ec1c86b7
Reviewed-on: https://skia-review.googlesource.com/8000
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
Reviewed-by: Ben Wagner <benjaminwagner@google.com>
diff --git a/src/sksl/SkSLCFGGenerator.cpp b/src/sksl/SkSLCFGGenerator.cpp
index e2c0380..6438d59 100644
--- a/src/sksl/SkSLCFGGenerator.cpp
+++ b/src/sksl/SkSLCFGGenerator.cpp
@@ -56,7 +56,7 @@
         const char* separator = "";
         for (auto iter = fBlocks[i].fBefore.begin(); iter != fBlocks[i].fBefore.end(); iter++) {
             printf("%s%s = %s", separator, iter->first->description().c_str(),
-                   *iter->second ? (*iter->second)->description().c_str() : "<undefined>");
+                   iter->second ? (*iter->second)->description().c_str() : "<undefined>");
             separator = ", ";
         }
         printf("\nEntrances: ");
@@ -68,9 +68,9 @@
         printf("\n");
         for (size_t j = 0; j < fBlocks[i].fNodes.size(); j++) {
             BasicBlock::Node& n = fBlocks[i].fNodes[j];
-            printf("Node %d: %s\n", (int) j, n.fKind == BasicBlock::Node::kExpression_Kind
-                                                           ? (*n.fExpression)->description().c_str()
-                                                           : n.fStatement->description().c_str());
+            printf("Node %d (%p): %s\n", (int) j, &n, n.fKind == BasicBlock::Node::kExpression_Kind
+                                                         ? (*n.expression())->description().c_str()
+                                                         : (*n.statement())->description().c_str());
         }
         printf("Exits: ");
         separator = "";
@@ -82,6 +82,207 @@
     }
 }
 
+bool BasicBlock::tryRemoveExpressionBefore(std::vector<BasicBlock::Node>::iterator* iter,
+                                           Expression* e) {
+    if (e->fKind == Expression::kTernary_Kind) {
+        return false;
+    }
+    bool result;
+    if ((*iter)->fKind == BasicBlock::Node::kExpression_Kind) {
+        ASSERT((*iter)->expression()->get() != e);
+        Expression* old = (*iter)->expression()->get();
+        do {
+            if ((*iter) == fNodes.begin()) {
+                ABORT("couldn't find %s before %s\n", e->description().c_str(),
+                                                      old->description().c_str());
+            }
+            --(*iter);
+        } while ((*iter)->fKind != BasicBlock::Node::kExpression_Kind ||
+                 (*iter)->expression()->get() != e);
+        result = this->tryRemoveExpression(iter);
+        while ((*iter)->fKind != BasicBlock::Node::kExpression_Kind ||
+               (*iter)->expression()->get() != old) {
+            ASSERT(*iter != fNodes.end());
+            ++(*iter);
+        }
+    } else {
+        Statement* old = (*iter)->statement()->get();
+        do {
+            if ((*iter) == fNodes.begin()) {
+                ABORT("couldn't find %s before %s\n", e->description().c_str(),
+                                                      old->description().c_str());
+            }
+            --(*iter);
+        } while ((*iter)->fKind != BasicBlock::Node::kExpression_Kind ||
+                 (*iter)->expression()->get() != e);
+        result = this->tryRemoveExpression(iter);
+        while ((*iter)->fKind != BasicBlock::Node::kStatement_Kind ||
+               (*iter)->statement()->get() != old) {
+            ASSERT(*iter != fNodes.end());
+            ++(*iter);
+        }
+    }
+    return result;
+}
+
+bool BasicBlock::tryRemoveLValueBefore(std::vector<BasicBlock::Node>::iterator* iter,
+                                       Expression* lvalue) {
+    switch (lvalue->fKind) {
+        case Expression::kVariableReference_Kind:
+            return true;
+        case Expression::kSwizzle_Kind:
+            return this->tryRemoveLValueBefore(iter, ((Swizzle*) lvalue)->fBase.get());
+        case Expression::kFieldAccess_Kind:
+            return this->tryRemoveLValueBefore(iter, ((FieldAccess*) lvalue)->fBase.get());
+        case Expression::kIndex_Kind:
+            if (!this->tryRemoveLValueBefore(iter, ((IndexExpression*) lvalue)->fBase.get())) {
+                return false;
+            }
+            return this->tryRemoveExpressionBefore(iter, ((IndexExpression*) lvalue)->fIndex.get());
+        default:
+            ABORT("invalid lvalue: %s\n", lvalue->description().c_str());
+    }
+}
+
+bool BasicBlock::tryRemoveExpression(std::vector<BasicBlock::Node>::iterator* iter) {
+    Expression* expr = (*iter)->expression()->get();
+    switch (expr->fKind) {
+        case Expression::kBinary_Kind: {
+            BinaryExpression* b = (BinaryExpression*) expr;
+            if (b->fOperator == Token::EQ) {
+                if (!this->tryRemoveLValueBefore(iter, b->fLeft.get())) {
+                    return false;
+                }
+            } else if (!this->tryRemoveExpressionBefore(iter, b->fLeft.get())) {
+                return false;
+            }
+            if (!this->tryRemoveExpressionBefore(iter, b->fRight.get())) {
+                return false;
+            }
+            ASSERT((*iter)->expression()->get() == expr);
+            *iter = fNodes.erase(*iter);
+            return true;
+        }
+        case Expression::kTernary_Kind: {
+            // ternaries cross basic block boundaries, must regenerate the CFG to remove it
+            return false;
+        }
+        case Expression::kFieldAccess_Kind: {
+            FieldAccess* f = (FieldAccess*) expr;
+            if (!this->tryRemoveExpressionBefore(iter, f->fBase.get())) {
+                return false;
+            }
+            *iter = fNodes.erase(*iter);
+            return true;
+        }
+        case Expression::kSwizzle_Kind: {
+            Swizzle* s = (Swizzle*) expr;
+            if (!this->tryRemoveExpressionBefore(iter, s->fBase.get())) {
+                return false;
+            }
+            *iter = fNodes.erase(*iter);
+            return true;
+        }
+        case Expression::kIndex_Kind: {
+            IndexExpression* idx = (IndexExpression*) expr;
+            if (!this->tryRemoveExpressionBefore(iter, idx->fBase.get())) {
+                return false;
+            }
+            if (!this->tryRemoveExpressionBefore(iter, idx->fIndex.get())) {
+                return false;
+            }
+            *iter = fNodes.erase(*iter);
+            return true;
+        }
+        case Expression::kConstructor_Kind: {
+            Constructor* c = (Constructor*) expr;
+            for (auto& arg : c->fArguments) {
+                if (!this->tryRemoveExpressionBefore(iter, arg.get())) {
+                    return false;
+                }
+                ASSERT((*iter)->expression()->get() == expr);
+            }
+            *iter = fNodes.erase(*iter);
+            return true;
+        }
+        case Expression::kFunctionCall_Kind: {
+            FunctionCall* f = (FunctionCall*) expr;
+            for (auto& arg : f->fArguments) {
+                if (!this->tryRemoveExpressionBefore(iter, arg.get())) {
+                    return false;
+                }
+                ASSERT((*iter)->expression()->get() == expr);
+            }
+            *iter = fNodes.erase(*iter);
+            return true;
+        }
+        case Expression::kPrefix_Kind:
+            if (!this->tryRemoveExpressionBefore(iter,
+                                                 ((PrefixExpression*) expr)->fOperand.get())) {
+                return false;
+            }
+            *iter = fNodes.erase(*iter);
+            return true;
+        case Expression::kPostfix_Kind:
+            if (!this->tryRemoveExpressionBefore(iter,
+                                                 ((PrefixExpression*) expr)->fOperand.get())) {
+                return false;
+            }
+            *iter = fNodes.erase(*iter);
+            return true;
+        case Expression::kBoolLiteral_Kind:  // fall through
+        case Expression::kFloatLiteral_Kind: // fall through
+        case Expression::kIntLiteral_Kind:   // fall through
+        case Expression::kVariableReference_Kind:
+            *iter = fNodes.erase(*iter);
+            return true;
+        default:
+            ABORT("unhandled expression: %s\n", expr->description().c_str());
+    }
+}
+
+bool BasicBlock::tryInsertExpression(std::vector<BasicBlock::Node>::iterator* iter,
+                                     std::unique_ptr<Expression>* expr) {
+    switch ((*expr)->fKind) {
+        case Expression::kBinary_Kind: {
+            BinaryExpression* b = (BinaryExpression*) expr->get();
+            if (!this->tryInsertExpression(iter, &b->fRight)) {
+                return false;
+            }
+            ++(*iter);
+            if (!this->tryInsertExpression(iter, &b->fLeft)) {
+                return false;
+            }
+            ++(*iter);
+            BasicBlock::Node node = { BasicBlock::Node::kExpression_Kind, true, expr, nullptr };
+            *iter = fNodes.insert(*iter, node);
+            return true;
+        }
+        case Expression::kBoolLiteral_Kind:  // fall through
+        case Expression::kFloatLiteral_Kind: // fall through
+        case Expression::kIntLiteral_Kind:   // fall through
+        case Expression::kVariableReference_Kind: {
+            BasicBlock::Node node = { BasicBlock::Node::kExpression_Kind, true, expr, nullptr };
+            *iter = fNodes.insert(*iter, node);
+            return true;
+        }
+        case Expression::kConstructor_Kind: {
+            Constructor* c = (Constructor*) expr->get();
+            for (auto& arg : c->fArguments) {
+                if (!this->tryInsertExpression(iter, &arg)) {
+                    return false;
+                }
+                ++(*iter);
+            }
+            BasicBlock::Node node = { BasicBlock::Node::kExpression_Kind, true, expr, nullptr };
+            *iter = fNodes.insert(*iter, node);
+            return true;
+        }
+        default:
+            return false;
+    }
+}
+
 void CFGGenerator::addExpression(CFG& cfg, std::unique_ptr<Expression>* e, bool constantPropagate) {
     ASSERT(e);
     switch ((*e)->fKind) {
@@ -182,6 +383,8 @@
         case Expression::kTernary_Kind: {
             TernaryExpression* t = (TernaryExpression*) e->get();
             this->addExpression(cfg, &t->fTest, constantPropagate);
+            cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind,
+                                                         constantPropagate, e, nullptr });
             BlockId start = cfg.fCurrent;
             cfg.newBlock();
             this->addExpression(cfg, &t->fIfTrue, constantPropagate);
@@ -223,24 +426,26 @@
     }
 }
 
-void CFGGenerator::addStatement(CFG& cfg, const Statement* s) {
-    switch (s->fKind) {
+void CFGGenerator::addStatement(CFG& cfg, std::unique_ptr<Statement>* s) {
+    switch ((*s)->fKind) {
         case Statement::kBlock_Kind:
-            for (const auto& child : ((const Block*) s)->fStatements) {
-                addStatement(cfg, child.get());
+            for (auto& child : ((Block&) **s).fStatements) {
+                addStatement(cfg, &child);
             }
             break;
         case Statement::kIf_Kind: {
-            IfStatement* ifs = (IfStatement*) s;
-            this->addExpression(cfg, &ifs->fTest, true);
+            IfStatement& ifs = (IfStatement&) **s;
+            this->addExpression(cfg, &ifs.fTest, true);
+            cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, false,
+                                                         nullptr, s });
             BlockId start = cfg.fCurrent;
             cfg.newBlock();
-            this->addStatement(cfg, ifs->fIfTrue.get());
+            this->addStatement(cfg, &ifs.fIfTrue);
             BlockId next = cfg.newBlock();
-            if (ifs->fIfFalse) {
+            if (ifs.fIfFalse) {
                 cfg.fCurrent = start;
                 cfg.newBlock();
-                this->addStatement(cfg, ifs->fIfFalse.get());
+                this->addStatement(cfg, &ifs.fIfFalse);
                 cfg.addExit(cfg.fCurrent, next);
                 cfg.fCurrent = next;
             } else {
@@ -249,14 +454,16 @@
             break;
         }
         case Statement::kExpression_Kind: {
-            this->addExpression(cfg, &((ExpressionStatement&) *s).fExpression, true);
+            this->addExpression(cfg, &((ExpressionStatement&) **s).fExpression, true);
+            cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, false,
+                                                         nullptr, s });
             break;
         }
         case Statement::kVarDeclarations_Kind: {
-            VarDeclarationsStatement& decls = ((VarDeclarationsStatement&) *s);
+            VarDeclarationsStatement& decls = ((VarDeclarationsStatement&) **s);
             for (auto& vd : decls.fDeclaration->fVars) {
-                if (vd.fValue) {
-                    this->addExpression(cfg, &vd.fValue, true);
+                if (vd->fValue) {
+                    this->addExpression(cfg, &vd->fValue, true);
                 }
             }
             cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, false,
@@ -269,7 +476,7 @@
             cfg.fCurrent = cfg.newIsolatedBlock();
             break;
         case Statement::kReturn_Kind: {
-            ReturnStatement& r = ((ReturnStatement&) *s);
+            ReturnStatement& r = ((ReturnStatement&) **s);
             if (r.fExpression) {
                 this->addExpression(cfg, &r.fExpression, true);
             }
@@ -291,16 +498,16 @@
             cfg.fCurrent = cfg.newIsolatedBlock();
             break;
         case Statement::kWhile_Kind: {
-            WhileStatement* w = (WhileStatement*) s;
+            WhileStatement& w = (WhileStatement&) **s;
             BlockId loopStart = cfg.newBlock();
             fLoopContinues.push(loopStart);
             BlockId loopExit = cfg.newIsolatedBlock();
             fLoopExits.push(loopExit);
-            this->addExpression(cfg, &w->fTest, true);
+            this->addExpression(cfg, &w.fTest, true);
             BlockId test = cfg.fCurrent;
             cfg.addExit(test, loopExit);
             cfg.newBlock();
-            this->addStatement(cfg, w->fStatement.get());
+            this->addStatement(cfg, &w.fStatement);
             cfg.addExit(cfg.fCurrent, loopStart);
             fLoopContinues.pop();
             fLoopExits.pop();
@@ -308,13 +515,13 @@
             break;
         }
         case Statement::kDo_Kind: {
-            DoStatement* d = (DoStatement*) s;
+            DoStatement& d = (DoStatement&) **s;
             BlockId loopStart = cfg.newBlock();
             fLoopContinues.push(loopStart);
             BlockId loopExit = cfg.newIsolatedBlock();
             fLoopExits.push(loopExit);
-            this->addStatement(cfg, d->fStatement.get());
-            this->addExpression(cfg, &d->fTest, true);
+            this->addStatement(cfg, &d.fStatement);
+            this->addExpression(cfg, &d.fTest, true);
             cfg.addExit(cfg.fCurrent, loopExit);
             cfg.addExit(cfg.fCurrent, loopStart);
             fLoopContinues.pop();
@@ -323,26 +530,26 @@
             break;
         }
         case Statement::kFor_Kind: {
-            ForStatement* f = (ForStatement*) s;
-            if (f->fInitializer) {
-                this->addStatement(cfg, f->fInitializer.get());
+            ForStatement& f = (ForStatement&) **s;
+            if (f.fInitializer) {
+                this->addStatement(cfg, &f.fInitializer);
             }
             BlockId loopStart = cfg.newBlock();
             BlockId next = cfg.newIsolatedBlock();
             fLoopContinues.push(next);
             BlockId loopExit = cfg.newIsolatedBlock();
             fLoopExits.push(loopExit);
-            if (f->fTest) {
-                this->addExpression(cfg, &f->fTest, true);
+            if (f.fTest) {
+                this->addExpression(cfg, &f.fTest, true);
                 BlockId test = cfg.fCurrent;
                 cfg.addExit(test, loopExit);
             }
             cfg.newBlock();
-            this->addStatement(cfg, f->fStatement.get());
+            this->addStatement(cfg, &f.fStatement);
             cfg.addExit(cfg.fCurrent, next);
             cfg.fCurrent = next;
-            if (f->fNext) {
-                this->addExpression(cfg, &f->fNext, true);
+            if (f.fNext) {
+                this->addExpression(cfg, &f.fNext, true);
             }
             cfg.addExit(cfg.fCurrent, loopStart);
             fLoopContinues.pop();
@@ -351,12 +558,12 @@
             break;
         }
         case Statement::kSwitch_Kind: {
-            SwitchStatement* ss = (SwitchStatement*) s;
-            this->addExpression(cfg, &ss->fValue, true);
+            SwitchStatement& ss = (SwitchStatement&) **s;
+            this->addExpression(cfg, &ss.fValue, true);
             BlockId start = cfg.fCurrent;
             BlockId switchExit = cfg.newIsolatedBlock();
             fLoopExits.push(switchExit);
-            for (const auto& c : ss->fCases) {
+            for (const auto& c : ss.fCases) {
                 cfg.newBlock();
                 cfg.addExit(start, cfg.fCurrent);
                 if (c->fValue) {
@@ -364,13 +571,13 @@
                     // because it must be constant. Not worth running two loops for.
                     this->addExpression(cfg, &c->fValue, true);
                 }
-                for (const auto& caseStatement : c->fStatements) {
-                    this->addStatement(cfg, caseStatement.get());
+                for (auto& caseStatement : c->fStatements) {
+                    this->addStatement(cfg, &caseStatement);
                 }
             }
             cfg.addExit(cfg.fCurrent, switchExit);
             // note that unlike GLSL, our grammar requires the default case to be last
-            if (0 == ss->fCases.size() || ss->fCases[ss->fCases.size() - 1]->fValue) {
+            if (0 == ss.fCases.size() || ss.fCases[ss.fCases.size() - 1]->fValue) {
                 // switch does not have a default clause, mark that it can skip straight to the end
                 cfg.addExit(start, switchExit);
             }
@@ -378,17 +585,19 @@
             cfg.fCurrent = switchExit;
             break;
         }
+        case Statement::kNop_Kind:
+            break;
         default:
-            printf("statement: %s\n", s->description().c_str());
+            printf("statement: %s\n", (*s)->description().c_str());
             ABORT("unsupported statement kind");
     }
 }
 
-CFG CFGGenerator::getCFG(const FunctionDefinition& f) {
+CFG CFGGenerator::getCFG(FunctionDefinition& f) {
     CFG result;
     result.fStart = result.newBlock();
     result.fCurrent = result.fStart;
-    this->addStatement(result, f.fBody.get());
+    this->addStatement(result, &f.fBody);
     result.newBlock();
     result.fExit = result.fCurrent;
     return result;
diff --git a/src/sksl/SkSLCFGGenerator.h b/src/sksl/SkSLCFGGenerator.h
index 0a03d69..885d926 100644
--- a/src/sksl/SkSLCFGGenerator.h
+++ b/src/sksl/SkSLCFGGenerator.h
@@ -26,6 +26,42 @@
             kExpression_Kind
         };
 
+        Node(Kind kind, bool constantPropagation, std::unique_ptr<Expression>* expression,
+             std::unique_ptr<Statement>* statement)
+        : fKind(kind)
+        , fConstantPropagation(constantPropagation)
+        , fExpression(expression)
+        , fStatement(statement) {}
+
+        std::unique_ptr<Expression>* expression() const {
+            ASSERT(fKind == kExpression_Kind);
+            return fExpression;
+        }
+
+        void setExpression(std::unique_ptr<Expression> expr) {
+            ASSERT(fKind == kExpression_Kind);
+            *fExpression = std::move(expr);
+        }
+
+        std::unique_ptr<Statement>* statement() const {
+            ASSERT(fKind == kStatement_Kind);
+            return fStatement;
+        }
+
+        void setStatement(std::unique_ptr<Statement> stmt) {
+            ASSERT(fKind == kStatement_Kind);
+            *fStatement = std::move(stmt);
+        }
+
+        String description() const {
+            if (fKind == kStatement_Kind) {
+                return (*fStatement)->description();
+            } else {
+                ASSERT(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
@@ -35,10 +71,45 @@
         // 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<Expression>* fExpression;
-        const Statement* fStatement;
+        std::unique_ptr<Statement>* 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<BasicBlock::Node>::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<BasicBlock::Node>::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<BasicBlock::Node>::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<BasicBlock::Node>::iterator* iter,
+                             std::unique_ptr<Expression>* expr);
+
     std::vector<Node> fNodes;
     std::set<BlockId> fEntrances;
     std::set<BlockId> fExits;
@@ -81,10 +152,10 @@
 public:
     CFGGenerator() {}
 
-    CFG getCFG(const FunctionDefinition& f);
+    CFG getCFG(FunctionDefinition& f);
 
 private:
-    void addStatement(CFG& cfg, const Statement* s);
+    void addStatement(CFG& cfg, std::unique_ptr<Statement>* s);
 
     void addExpression(CFG& cfg, std::unique_ptr<Expression>* e, bool constantPropagate);
 
diff --git a/src/sksl/SkSLCompiler.cpp b/src/sksl/SkSLCompiler.cpp
index ea87e99..e4ab700 100644
--- a/src/sksl/SkSLCompiler.cpp
+++ b/src/sksl/SkSLCompiler.cpp
@@ -14,9 +14,12 @@
 #include "SkSLParser.h"
 #include "SkSLSPIRVCodeGenerator.h"
 #include "ir/SkSLExpression.h"
+#include "ir/SkSLExpressionStatement.h"
 #include "ir/SkSLIntLiteral.h"
 #include "ir/SkSLModifiersDeclaration.h"
+#include "ir/SkSLNop.h"
 #include "ir/SkSLSymbolTable.h"
+#include "ir/SkSLTernaryExpression.h"
 #include "ir/SkSLUnresolvedFunction.h"
 #include "ir/SkSLVarDeclarations.h"
 
@@ -207,8 +210,8 @@
                               DefinitionMap* definitions) {
     switch (node.fKind) {
         case BasicBlock::Node::kExpression_Kind: {
-            ASSERT(node.fExpression);
-            const Expression* expr = (Expression*) node.fExpression->get();
+            ASSERT(node.expression());
+            const Expression* expr = (Expression*) node.expression()->get();
             switch (expr->fKind) {
                 case Expression::kBinary_Kind: {
                     BinaryExpression* b = (BinaryExpression*) expr;
@@ -240,22 +243,30 @@
                                        p->fOperand.get(),
                                        (std::unique_ptr<Expression>*) &fContext.fDefined_Expression,
                                        definitions);
-
                     }
                     break;
                 }
+                case Expression::kVariableReference_Kind: {
+                    const VariableReference* v = (VariableReference*) expr;
+                    if (v->fRefKind != VariableReference::kRead_RefKind) {
+                        this->addDefinition(
+                                       v,
+                                       (std::unique_ptr<Expression>*) &fContext.fDefined_Expression,
+                                       definitions);
+                    }
+                }
                 default:
                     break;
             }
             break;
         }
         case BasicBlock::Node::kStatement_Kind: {
-            const Statement* stmt = (Statement*) node.fStatement;
+            const Statement* stmt = (Statement*) node.statement()->get();
             if (stmt->fKind == Statement::kVarDeclarations_Kind) {
                 VarDeclarationsStatement* vd = (VarDeclarationsStatement*) stmt;
-                for (VarDeclaration& decl : vd->fDeclaration->fVars) {
-                    if (decl.fValue) {
-                        (*definitions)[decl.fVar] = &decl.fValue;
+                for (const auto& decl : vd->fDeclaration->fVars) {
+                    if (decl->fValue) {
+                        (*definitions)[decl->fVar] = &decl->fValue;
                     }
                 }
             }
@@ -308,12 +319,12 @@
     for (const auto& block : cfg.fBlocks) {
         for (const auto& node : block.fNodes) {
             if (node.fKind == BasicBlock::Node::kStatement_Kind) {
-                ASSERT(node.fStatement);
-                const Statement* s = node.fStatement;
+                ASSERT(node.statement());
+                const Statement* s = node.statement()->get();
                 if (s->fKind == Statement::kVarDeclarations_Kind) {
                     const VarDeclarationsStatement* vd = (const VarDeclarationsStatement*) s;
-                    for (const VarDeclaration& decl : vd->fDeclaration->fVars) {
-                        result[decl.fVar] = nullptr;
+                    for (const auto& decl : vd->fDeclaration->fVars) {
+                        result[decl->fVar] = nullptr;
                     }
                 }
             }
@@ -322,20 +333,290 @@
     return result;
 }
 
-void Compiler::scanCFG(const FunctionDefinition& f) {
-    CFG cfg = CFGGenerator().getCFG(f);
+/**
+ * Returns true if assigning to this lvalue has no effect.
+ */
+static bool is_dead(const Expression& lvalue) {
+    switch (lvalue.fKind) {
+        case Expression::kVariableReference_Kind:
+            return ((VariableReference&) lvalue).fVariable.dead();
+        case Expression::kSwizzle_Kind:
+            return is_dead(*((Swizzle&) lvalue).fBase);
+        case Expression::kFieldAccess_Kind:
+            return is_dead(*((FieldAccess&) lvalue).fBase);
+        case Expression::kIndex_Kind: {
+            const IndexExpression& idx = (IndexExpression&) lvalue;
+            return is_dead(*idx.fBase) && !idx.fIndex->hasSideEffects();
+        }
+        default:
+            ABORT("invalid lvalue: %s\n", lvalue.description().c_str());
+    }
+}
 
-    // compute the data flow
-    cfg.fBlocks[cfg.fStart].fBefore = compute_start_state(cfg);
+/**
+ * Returns true if this is an assignment which can be collapsed down to just the right hand side due
+ * to a dead target and lack of side effects on the left hand side.
+ */
+static bool dead_assignment(const BinaryExpression& b) {
+    if (!Token::IsAssignment(b.fOperator)) {
+        return false;
+    }
+    return is_dead(*b.fLeft);
+}
+
+void Compiler::computeDataFlow(CFG* cfg) {
+    cfg->fBlocks[cfg->fStart].fBefore = compute_start_state(*cfg);
     std::set<BlockId> workList;
-    for (BlockId i = 0; i < cfg.fBlocks.size(); i++) {
+    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);
+        this->scanCFG(cfg, next, &workList);
     }
+}
+
+/**
+ * Attempts to replace the expression pointed to by iter with a new one (in both the CFG and the
+ * IR). If the expression can be cleanly removed, returns true and updates the iterator to point to
+ * the newly-inserted element. Otherwise updates only the IR and returns false (and the CFG will
+ * need to be regenerated).
+ */
+bool try_replace_expression(BasicBlock* b,
+                            std::vector<BasicBlock::Node>::iterator* iter,
+                            std::unique_ptr<Expression>* newExpression) {
+    std::unique_ptr<Expression>* target = (*iter)->expression();
+    if (!b->tryRemoveExpression(iter)) {
+        *target = std::move(*newExpression);
+        return false;
+    }
+    *target = std::move(*newExpression);
+    return b->tryInsertExpression(iter, target);
+}
+
+/**
+ * Returns true if the expression is a constant numeric literal with the specified value.
+ */
+bool is_constant(Expression& expr, double value) {
+    switch (expr.fKind) {
+        case Expression::kIntLiteral_Kind:
+            return ((IntLiteral&) expr).fValue == value;
+        case Expression::kFloatLiteral_Kind:
+            return ((FloatLiteral&) expr).fValue == value;
+        default:
+            return false;
+    }
+}
+
+/**
+ * Collapses the binary expression pointed to by iter down to just the right side (in both the IR
+ * and CFG structures).
+ */
+void delete_left(BasicBlock* b,
+                 std::vector<BasicBlock::Node>::iterator* iter,
+                 bool* outUpdated,
+                 bool* outNeedsRescan) {
+    *outUpdated = true;
+    if (!try_replace_expression(b, iter, &((BinaryExpression&) **(*iter)->expression()).fRight)) {
+        *outNeedsRescan = true;
+    }
+}
+
+/**
+ * Collapses the binary expression pointed to by iter down to just the left side (in both the IR and
+ * CFG structures).
+ */
+void delete_right(BasicBlock* b,
+                  std::vector<BasicBlock::Node>::iterator* iter,
+                  bool* outUpdated,
+                  bool* outNeedsRescan) {
+    *outUpdated = true;
+    if (!try_replace_expression(b, iter, &((BinaryExpression&) **(*iter)->expression()).fLeft)) {
+        *outNeedsRescan = true;
+    }
+}
+
+void Compiler::simplifyExpression(DefinitionMap& definitions,
+                                  BasicBlock& b,
+                                  std::vector<BasicBlock::Node>::iterator* iter,
+                                  std::unordered_set<const Variable*>* undefinedVariables,
+                                  bool* outUpdated,
+                                  bool* outNeedsRescan) {
+    Expression* expr = (*iter)->expression()->get();
+    ASSERT(expr);
+    if ((*iter)->fConstantPropagation) {
+        std::unique_ptr<Expression> optimized = expr->constantPropagate(*fIRGenerator, definitions);
+        if (optimized) {
+            if (!try_replace_expression(&b, iter, &optimized)) {
+                *outNeedsRescan = true;
+            }
+            ASSERT((*iter)->fKind == BasicBlock::Node::kExpression_Kind);
+            expr = (*iter)->expression()->get();
+            *outUpdated = true;
+        }
+    }
+    switch (expr->fKind) {
+        case Expression::kVariableReference_Kind: {
+            const Variable& var = ((VariableReference*) expr)->fVariable;
+            if (var.fStorage == Variable::kLocal_Storage && !definitions[&var] &&
+                (*undefinedVariables).find(&var) == (*undefinedVariables).end()) {
+                (*undefinedVariables).insert(&var);
+                this->error(expr->fPosition,
+                            "'" + var.fName + "' has not been assigned");
+            }
+            break;
+        }
+        case Expression::kTernary_Kind: {
+            TernaryExpression* t = (TernaryExpression*) expr;
+            if (t->fTest->fKind == Expression::kBoolLiteral_Kind) {
+                // ternary has a constant test, replace it with either the true or
+                // false branch
+                if (((BoolLiteral&) *t->fTest).fValue) {
+                    (*iter)->setExpression(std::move(t->fIfTrue));
+                } else {
+                    (*iter)->setExpression(std::move(t->fIfFalse));
+                }
+                *outUpdated = true;
+                *outNeedsRescan = true;
+            }
+            break;
+        }
+        case Expression::kBinary_Kind: {
+            // collapse useless expressions like x * 1 or x + 0
+            BinaryExpression* bin = (BinaryExpression*) expr;
+            switch (bin->fOperator) {
+                case Token::STAR:
+                    if (is_constant(*bin->fLeft, 1)) {
+                        delete_left(&b, iter, outUpdated, outNeedsRescan);
+                    }
+                    else if (is_constant(*bin->fRight, 1)) {
+                        delete_right(&b, iter, outUpdated, outNeedsRescan);
+                    }
+                    break;
+                case Token::PLUS: // fall through
+                case Token::MINUS:
+                    if (is_constant(*bin->fLeft, 0)) {
+                        delete_left(&b, iter, outUpdated, outNeedsRescan);
+                    }
+                    else if (is_constant(*bin->fRight, 0)) {
+                        delete_right(&b, iter, outUpdated, outNeedsRescan);
+                    }
+                    break;
+                case Token::SLASH:
+                    if (is_constant(*bin->fRight, 1)) {
+                        delete_right(&b, iter, outUpdated, outNeedsRescan);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+        default:
+            break;
+    }
+}
+
+void Compiler::simplifyStatement(DefinitionMap& definitions,
+                                  BasicBlock& b,
+                                  std::vector<BasicBlock::Node>::iterator* iter,
+                                  std::unordered_set<const Variable*>* undefinedVariables,
+                                  bool* outUpdated,
+                                  bool* outNeedsRescan) {
+    Statement* stmt = (*iter)->statement()->get();
+    switch (stmt->fKind) {
+        case Statement::kVarDeclarations_Kind: {
+            VarDeclarations& vd = *((VarDeclarationsStatement&) *stmt).fDeclaration;
+            for (auto varIter = vd.fVars.begin(); varIter != vd.fVars.end(); ) {
+                const auto& varDecl = **varIter;
+                if (varDecl.fVar->dead() &&
+                    (!varDecl.fValue ||
+                     !varDecl.fValue->hasSideEffects())) {
+                    if (varDecl.fValue) {
+                        ASSERT((*iter)->statement()->get() == stmt);
+                        if (!b.tryRemoveExpressionBefore(iter, varDecl.fValue.get())) {
+                            *outNeedsRescan = true;
+                        }
+                    }
+                    varIter = vd.fVars.erase(varIter);
+                    *outUpdated = true;
+                } else {
+                    ++varIter;
+                }
+            }
+            if (vd.fVars.size() == 0) {
+                (*iter)->setStatement(std::unique_ptr<Statement>(new Nop()));
+            }
+            break;
+        }
+        case Statement::kIf_Kind: {
+            IfStatement& i = (IfStatement&) *stmt;
+            if (i.fIfFalse && i.fIfFalse->isEmpty()) {
+                // else block doesn't do anything, remove it
+                i.fIfFalse.reset();
+                *outUpdated = true;
+                *outNeedsRescan = true;
+            }
+            if (!i.fIfFalse && i.fIfTrue->isEmpty()) {
+                // if block doesn't do anything, no else block
+                if (i.fTest->hasSideEffects()) {
+                    // test has side effects, keep it
+                    (*iter)->setStatement(std::unique_ptr<Statement>(
+                                                      new ExpressionStatement(std::move(i.fTest))));
+                } else {
+                    // no if, no else, no test side effects, kill the whole if
+                    // statement
+                    (*iter)->setStatement(std::unique_ptr<Statement>(new Nop()));
+                }
+                *outUpdated = true;
+                *outNeedsRescan = true;
+            }
+            break;
+        }
+        case Statement::kExpression_Kind: {
+            ExpressionStatement& e = (ExpressionStatement&) *stmt;
+            ASSERT((*iter)->statement()->get() == &e);
+            if (e.fExpression->fKind == Expression::kBinary_Kind) {
+                BinaryExpression& bin = (BinaryExpression&) *e.fExpression;
+                if (dead_assignment(bin)) {
+                    if (!b.tryRemoveExpressionBefore(iter, &bin)) {
+                        *outNeedsRescan = true;
+                    }
+                    if (bin.fRight->hasSideEffects()) {
+                        // still have to evaluate the right due to side effects,
+                        // replace the binary expression with just the right side
+                        e.fExpression = std::move(bin.fRight);
+                        if (!b.tryInsertExpression(iter, &e.fExpression)) {
+                            *outNeedsRescan = true;
+                        }
+                    } else {
+                        // no side effects, kill the whole statement
+                        ASSERT((*iter)->statement()->get() == stmt);
+                        (*iter)->setStatement(std::unique_ptr<Statement>(new Nop()));
+                    }
+                    *outUpdated = true;
+                    break;
+                }
+            }
+            if (!e.fExpression->hasSideEffects()) {
+                // Expression statement with no side effects, kill it
+                if (!b.tryRemoveExpressionBefore(iter, e.fExpression.get())) {
+                    *outNeedsRescan = true;
+                }
+                ASSERT((*iter)->statement()->get() == stmt);
+                (*iter)->setStatement(std::unique_ptr<Statement>(new Nop()));
+                *outUpdated = true;
+            }
+            break;
+        }
+        default:
+            break;
+    }
+}
+
+void Compiler::scanCFG(FunctionDefinition& f) {
+    CFG cfg = CFGGenerator().getCFG(f);
+    this->computeDataFlow(&cfg);
 
     // check for unreachable code
     for (size_t i = 0; i < cfg.fBlocks.size(); i++) {
@@ -344,10 +625,10 @@
             Position p;
             switch (cfg.fBlocks[i].fNodes[0].fKind) {
                 case BasicBlock::Node::kStatement_Kind:
-                    p = cfg.fBlocks[i].fNodes[0].fStatement->fPosition;
+                    p = (*cfg.fBlocks[i].fNodes[0].statement())->fPosition;
                     break;
                 case BasicBlock::Node::kExpression_Kind:
-                    p = (*cfg.fBlocks[i].fNodes[0].fExpression)->fPosition;
+                    p = (*cfg.fBlocks[i].fNodes[0].expression())->fPosition;
                     break;
             }
             this->error(p, String("unreachable"));
@@ -357,33 +638,34 @@
         return;
     }
 
-    // check for undefined variables, perform constant propagation
-    for (BasicBlock& b : cfg.fBlocks) {
-        DefinitionMap definitions = b.fBefore;
-        for (BasicBlock::Node& n : b.fNodes) {
-            if (n.fKind == BasicBlock::Node::kExpression_Kind) {
-                ASSERT(n.fExpression);
-                Expression* expr = n.fExpression->get();
-                if (n.fConstantPropagation) {
-                    std::unique_ptr<Expression> optimized = expr->constantPropagate(*fIRGenerator,
-                                                                                    definitions);
-                    if (optimized) {
-                        n.fExpression->reset(optimized.release());
-                        expr = n.fExpression->get();
-                    }
-                }
-                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 dead code & undefined variables, perform constant propagation
+    std::unordered_set<const Variable*> undefinedVariables;
+    bool updated;
+    bool needsRescan = false;
+    do {
+        if (needsRescan) {
+            cfg = CFGGenerator().getCFG(f);
+            this->computeDataFlow(&cfg);
+            needsRescan = false;
         }
-    }
+
+        updated = false;
+        for (BasicBlock& b : cfg.fBlocks) {
+            DefinitionMap definitions = b.fBefore;
+
+            for (auto iter = b.fNodes.begin(); iter != b.fNodes.end() && !needsRescan; ++iter) {
+                if (iter->fKind == BasicBlock::Node::kExpression_Kind) {
+                    this->simplifyExpression(definitions, b, &iter, &undefinedVariables, &updated,
+                                             &needsRescan);
+                } else {
+                    this->simplifyStatement(definitions, b, &iter, &undefinedVariables, &updated,
+                                             &needsRescan);
+                }
+                this->addDefinitions(*iter, &definitions);
+            }
+        }
+    } while (updated);
+    ASSERT(!needsRescan);
 
     // check for missing return
     if (f.fDeclaration.fReturnType != *fContext.fVoid_Type) {
diff --git a/src/sksl/SkSLCompiler.h b/src/sksl/SkSLCompiler.h
index 7e3c83e..5e7bc9e 100644
--- a/src/sksl/SkSLCompiler.h
+++ b/src/sksl/SkSLCompiler.h
@@ -9,6 +9,7 @@
 #define SKSL_COMPILER
 
 #include <set>
+#include <unordered_set>
 #include <vector>
 #include "ir/SkSLProgram.h"
 #include "ir/SkSLSymbolTable.h"
@@ -71,7 +72,31 @@
 
     void scanCFG(CFG* cfg, BlockId block, std::set<BlockId>* workList);
 
-    void scanCFG(const FunctionDefinition& f);
+    void computeDataFlow(CFG* cfg);
+
+    /**
+     * Simplifies the expression pointed to by iter (in both the IR and CFG structures), if
+     * possible.
+     */
+    void simplifyExpression(DefinitionMap& definitions,
+                            BasicBlock& b,
+                            std::vector<BasicBlock::Node>::iterator* iter,
+                            std::unordered_set<const Variable*>* undefinedVariables,
+                            bool* outUpdated,
+                            bool* outNeedsRescan);
+
+    /**
+     * Simplifies the statement pointed to by iter (in both the IR and CFG structures), if
+     * possible.
+     */
+    void simplifyStatement(DefinitionMap& definitions,
+                           BasicBlock& b,
+                           std::vector<BasicBlock::Node>::iterator* iter,
+                           std::unordered_set<const Variable*>* undefinedVariables,
+                           bool* outUpdated,
+                           bool* outNeedsRescan);
+
+    void scanCFG(FunctionDefinition& f);
 
     void internalConvertProgram(String text,
                                 Modifiers::Flag* defaultPrecision,
diff --git a/src/sksl/SkSLContext.h b/src/sksl/SkSLContext.h
index 450baa5..a9ebe9d 100644
--- a/src/sksl/SkSLContext.h
+++ b/src/sksl/SkSLContext.h
@@ -283,6 +283,10 @@
         virtual String description() const override {
             return String("<defined>");
         }
+        
+        bool hasSideEffects() const override {
+            return false;
+        }
 
         typedef Expression INHERITED;
     };
diff --git a/src/sksl/SkSLGLSLCodeGenerator.cpp b/src/sksl/SkSLGLSLCodeGenerator.cpp
index ab64e66..8099d45 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.cpp
+++ b/src/sksl/SkSLGLSLCodeGenerator.cpp
@@ -14,6 +14,7 @@
 #include "ir/SkSLExtension.h"
 #include "ir/SkSLIndexExpression.h"
 #include "ir/SkSLModifiersDeclaration.h"
+#include "ir/SkSLNop.h"
 #include "ir/SkSLVariableReference.h"
 
 namespace SkSL {
@@ -509,10 +510,7 @@
     StringStream buffer;
     fOut = &buffer;
     fIndentation++;
-    for (const auto& s : f.fBody->fStatements) {
-        this->writeStatement(*s);
-        this->writeLine();
-    }
+    this->writeStatements(((Block&) *f.fBody).fStatements);
     fIndentation--;
     this->writeLine("}");
 
@@ -620,26 +618,26 @@
 
 void GLSLCodeGenerator::writeVarDeclarations(const VarDeclarations& decl, bool global) {
     ASSERT(decl.fVars.size() > 0);
-    this->writeModifiers(decl.fVars[0].fVar->fModifiers, global);
+    this->writeModifiers(decl.fVars[0]->fVar->fModifiers, global);
     this->writeType(decl.fBaseType);
     String separator(" ");
     for (const auto& var : decl.fVars) {
-        ASSERT(var.fVar->fModifiers == decl.fVars[0].fVar->fModifiers);
+        ASSERT(var->fVar->fModifiers == decl.fVars[0]->fVar->fModifiers);
         this->write(separator);
         separator = String(", ");
-        this->write(var.fVar->fName);
-        for (const auto& size : var.fSizes) {
+        this->write(var->fVar->fName);
+        for (const auto& size : var->fSizes) {
             this->write("[");
             if (size) {
                 this->writeExpression(*size, kTopLevel_Precedence);
             }
             this->write("]");
         }
-        if (var.fValue) {
+        if (var->fValue) {
             this->write(" = ");
-            this->writeExpression(*var.fValue, kTopLevel_Precedence);
+            this->writeExpression(*var->fValue, kTopLevel_Precedence);
         }
-        if (!fFoundImageDecl && var.fVar->fType == *fContext.fImage2D_Type) {
+        if (!fFoundImageDecl && var->fVar->fType == *fContext.fImage2D_Type) {
             if (fProgram.fSettings.fCaps->imageLoadStoreExtensionString()) {
                 fHeader.writeText("#extension ");
                 fHeader.writeText(fProgram.fSettings.fCaps->imageLoadStoreExtensionString());
@@ -690,18 +688,27 @@
         case Statement::kDiscard_Kind:
             this->write("discard;");
             break;
+        case Statement::kNop_Kind:
+            this->write(";");
+            break;
         default:
             ABORT("unsupported statement: %s", s.description().c_str());
     }
 }
 
+void GLSLCodeGenerator::writeStatements(const std::vector<std::unique_ptr<Statement>>& statements) {
+    for (const auto& s : statements) {
+        if (!s->isEmpty()) {
+            this->writeStatement(*s);
+            this->writeLine();
+        }
+    }
+}
+
 void GLSLCodeGenerator::writeBlock(const Block& b) {
     this->writeLine("{");
     fIndentation++;
-    for (const auto& s : b.fStatements) {
-        this->writeStatement(*s);
-        this->writeLine();
-    }
+    this->writeStatements(b.fStatements);
     fIndentation--;
     this->write("}");
 }
@@ -821,7 +828,7 @@
             case ProgramElement::kVar_Kind: {
                 VarDeclarations& decl = (VarDeclarations&) *e;
                 if (decl.fVars.size() > 0) {
-                    int builtin = decl.fVars[0].fVar->fModifiers.fLayout.fBuiltin;
+                    int builtin = decl.fVars[0]->fVar->fModifiers.fLayout.fBuiltin;
                     if (builtin == -1) {
                         // normal var
                         this->writeVarDeclarations(decl, true);
diff --git a/src/sksl/SkSLGLSLCodeGenerator.h b/src/sksl/SkSLGLSLCodeGenerator.h
index ab88d50..65be7dc 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.h
+++ b/src/sksl/SkSLGLSLCodeGenerator.h
@@ -145,6 +145,8 @@
 
     void writeStatement(const Statement& s);
 
+    void writeStatements(const std::vector<std::unique_ptr<Statement>>& statements);
+
     void writeBlock(const Block& b);
 
     void writeIfStatement(const IfStatement& stmt);
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 975b05f..56858a9 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -210,7 +210,7 @@
 
 std::unique_ptr<VarDeclarations> IRGenerator::convertVarDeclarations(const ASTVarDeclarations& decl,
                                                                      Variable::Storage storage) {
-    std::vector<VarDeclaration> variables;
+    std::vector<std::unique_ptr<VarDeclaration>> variables;
     const Type* baseType = this->convertType(*decl.fType);
     if (!baseType) {
         return nullptr;
@@ -254,6 +254,7 @@
                 return nullptr;
             }
             value = this->coerce(std::move(value), *type);
+            var->fWriteCount = 1;
         }
         if (storage == Variable::kGlobal_Storage && varDecl.fName == String("sk_FragColor") &&
             (*fSymbolTable)[varDecl.fName]) {
@@ -265,7 +266,8 @@
             Variable* old = (Variable*) (*fSymbolTable)[varDecl.fName];
             old->fModifiers = var->fModifiers;
         } else {
-            variables.emplace_back(var.get(), std::move(sizes), std::move(value));
+            variables.emplace_back(new VarDeclaration(var.get(), std::move(sizes),
+                                                      std::move(value)));
             fSymbolTable->add(varDecl.fName, std::move(var));
         }
     }
@@ -542,7 +544,8 @@
                 }
                 if (match) {
                     if (*returnType != other->fReturnType) {
-                        FunctionDeclaration newDecl(f.fPosition, f.fName, parameters, *returnType);
+                        FunctionDeclaration newDecl(f.fPosition, f.fModifiers, f.fName, parameters,
+                                                    *returnType);
                         fErrors.error(f.fPosition, "functions '" + newDecl.description() +
                                                    "' and '" + other->description() +
                                                    "' differ only in return type");
@@ -570,6 +573,7 @@
     if (!decl) {
         // couldn't find an existing declaration
         auto newDecl = std::unique_ptr<FunctionDeclaration>(new FunctionDeclaration(f.fPosition,
+                                                                                    f.fModifiers,
                                                                                     f.fName,
                                                                                     parameters,
                                                                                     *returnType));
@@ -590,6 +594,8 @@
         if (!body) {
             return nullptr;
         }
+        // conservatively assume all user-defined functions have side effects
+        ((Modifiers&) decl->fModifiers).fFlags |= Modifiers::kHasSideEffects_Flag;
         return std::unique_ptr<FunctionDefinition>(new FunctionDefinition(f.fPosition, *decl,
                                                                           std::move(body)));
     }
@@ -608,16 +614,16 @@
             return nullptr;
         }
         for (const auto& var : decl->fVars) {
-            fields.push_back(Type::Field(var.fVar->fModifiers, var.fVar->fName,
-                                         &var.fVar->fType));
-            if (var.fValue) {
+            fields.push_back(Type::Field(var->fVar->fModifiers, var->fVar->fName,
+                                         &var->fVar->fType));
+            if (var->fValue) {
                 fErrors.error(decl->fPosition,
                               "initializers are not permitted on interface block fields");
             }
-            if (var.fVar->fModifiers.fFlags & (Modifiers::kIn_Flag |
-                                               Modifiers::kOut_Flag |
-                                               Modifiers::kUniform_Flag |
-                                               Modifiers::kConst_Flag)) {
+            if (var->fVar->fModifiers.fFlags & (Modifiers::kIn_Flag |
+                                                Modifiers::kOut_Flag |
+                                                Modifiers::kUniform_Flag |
+                                                Modifiers::kConst_Flag)) {
                 fErrors.error(decl->fPosition,
                               "interface block fields may not have storage qualifiers");
             }
@@ -944,6 +950,9 @@
 std::unique_ptr<Expression> IRGenerator::constantFold(const Expression& left,
                                                       Token::Kind op,
                                                       const Expression& right) const {
+    if (!left.isConstant() || !right.isConstant()) {
+        return nullptr;
+    }
     // Note that we expressly do not worry about precision and overflow here -- we use the maximum
     // precision to calculate the results and hope the result makes sense. The plan is to move the
     // Skia caps into SkSL, so we have access to all of them including the precisions of the various
@@ -967,9 +976,9 @@
         int64_t leftVal  = ((IntLiteral&) left).fValue;
         int64_t rightVal = ((IntLiteral&) right).fValue;
         switch (op) {
-            case Token::PLUS:       return RESULT(Int,  +);
-            case Token::MINUS:      return RESULT(Int,  -);
-            case Token::STAR:       return RESULT(Int,  *);
+            case Token::PLUS:       return RESULT(Int, +);
+            case Token::MINUS:      return RESULT(Int, -);
+            case Token::STAR:       return RESULT(Int, *);
             case Token::SLASH:
                 if (rightVal) {
                     return RESULT(Int, /);
@@ -1010,15 +1019,37 @@
                 }
                 fErrors.error(right.fPosition, "division by zero");
                 return nullptr;
-            case Token::EQEQ:       return RESULT(Bool,  ==);
-            case Token::NEQ:        return RESULT(Bool,  !=);
-            case Token::GT:         return RESULT(Bool,  >);
-            case Token::GTEQ:       return RESULT(Bool,  >=);
-            case Token::LT:         return RESULT(Bool,  <);
-            case Token::LTEQ:       return RESULT(Bool,  <=);
+            case Token::EQEQ:       return RESULT(Bool, ==);
+            case Token::NEQ:        return RESULT(Bool, !=);
+            case Token::GT:         return RESULT(Bool, >);
+            case Token::GTEQ:       return RESULT(Bool, >=);
+            case Token::LT:         return RESULT(Bool, <);
+            case Token::LTEQ:       return RESULT(Bool, <=);
             default:                return nullptr;
         }
     }
+    if (left.fType.kind() == Type::kVector_Kind &&
+        left.fType.componentType() == *fContext.fFloat_Type &&
+        left.fType == right.fType) {
+        ASSERT(left.fKind  == Expression::kConstructor_Kind);
+        ASSERT(right.fKind == Expression::kConstructor_Kind);
+        std::vector<std::unique_ptr<Expression>> args;
+        #define RETURN_VEC_COMPONENTWISE_RESULT(op)                                    \
+            for (int i = 0; i < left.fType.columns(); i++) {                           \
+                float value = ((Constructor&) left).getFVecComponent(i) op             \
+                              ((Constructor&) right).getFVecComponent(i);              \
+                args.emplace_back(new FloatLiteral(fContext, Position(), value));      \
+            }                                                                          \
+            return std::unique_ptr<Expression>(new Constructor(Position(), left.fType, \
+                                                               std::move(args)));
+        switch (op) {
+            case Token::PLUS:  RETURN_VEC_COMPONENTWISE_RESULT(+);
+            case Token::MINUS: RETURN_VEC_COMPONENTWISE_RESULT(-);
+            case Token::STAR:  RETURN_VEC_COMPONENTWISE_RESULT(*);
+            case Token::SLASH: RETURN_VEC_COMPONENTWISE_RESULT(/);
+            default:           return nullptr;
+        }
+    }
     #undef RESULT
     return nullptr;
 }
@@ -1177,7 +1208,8 @@
             return nullptr;
         }
         if (arguments[i] && (function.fParameters[i]->fModifiers.fFlags & Modifiers::kOut_Flag)) {
-            this->markWrittenTo(*arguments[i], true);
+            this->markWrittenTo(*arguments[i], 
+                                function.fParameters[i]->fModifiers.fFlags & Modifiers::kIn_Flag);
         }
     }
     if (function.fBuiltin && function.fName == "texture" &&
diff --git a/src/sksl/SkSLParser.cpp b/src/sksl/SkSLParser.cpp
index 0f2b943..ae9d990 100644
--- a/src/sksl/SkSLParser.cpp
+++ b/src/sksl/SkSLParser.cpp
@@ -309,7 +309,7 @@
     if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
         return nullptr;
     }
-    if (!modifiers.fFlags && this->peek().fKind == Token::LPAREN) {
+    if (this->peek().fKind == Token::LPAREN) {
         this->nextToken();
         std::vector<std::unique_ptr<ASTParameter>> parameters;
         while (this->peek().fKind != Token::RPAREN) {
@@ -334,7 +334,9 @@
                 return nullptr;
             }
         }
-        return std::unique_ptr<ASTDeclaration>(new ASTFunction(name.fPosition, std::move(type),
+        return std::unique_ptr<ASTDeclaration>(new ASTFunction(name.fPosition,
+                                                               modifiers,
+                                                               std::move(type),
                                                                std::move(name.fText),
                                                                std::move(parameters),
                                                                std::move(body)));
@@ -721,6 +723,10 @@
                 this->nextToken();
                 flags |= Modifiers::kRestrict_Flag;
                 break;
+            case Token::HASSIDEEFFECTS:
+                this->nextToken();
+                flags |= Modifiers::kHasSideEffects_Flag;
+                break;
             default:
                 return Modifiers(layout, flags);
         }
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.cpp b/src/sksl/SkSLSPIRVCodeGenerator.cpp
index d713d6f..d2850e9 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/SkSLSPIRVCodeGenerator.cpp
@@ -2461,7 +2461,7 @@
         write_stringstream(fGlobalInitializersBuffer, out);
     }
     StringStream bodyBuffer;
-    this->writeBlock(*f.fBody, bodyBuffer);
+    this->writeBlock((Block&) *f.fBody, bodyBuffer);
     write_stringstream(fVariableBuffer, out);
     write_stringstream(bodyBuffer, out);
     if (fCurrentBlock) {
@@ -2558,7 +2558,7 @@
 void SPIRVCodeGenerator::writeGlobalVars(Program::Kind kind, const VarDeclarations& decl,
                                          OutputStream& out) {
     for (size_t i = 0; i < decl.fVars.size(); i++) {
-        const VarDeclaration& varDecl = decl.fVars[i];
+        const VarDeclaration& varDecl = *decl.fVars[i];
         const Variable* var = varDecl.fVar;
         // These haven't been implemented in our SPIR-V generator yet and we only currently use them
         // in the OpenGL backend.
@@ -2620,7 +2620,7 @@
 
 void SPIRVCodeGenerator::writeVarDeclarations(const VarDeclarations& decl, OutputStream& out) {
     for (const auto& varDecl : decl.fVars) {
-        const Variable* var = varDecl.fVar;
+        const Variable* var = varDecl->fVar;
         // These haven't been implemented in our SPIR-V generator yet and we only currently use them
         // in the OpenGL backend.
         ASSERT(!(var->fModifiers.fFlags & (Modifiers::kReadOnly_Flag |
@@ -2633,8 +2633,8 @@
         SpvId type = this->getPointerType(var->fType, SpvStorageClassFunction);
         this->writeInstruction(SpvOpVariable, type, id, SpvStorageClassFunction, fVariableBuffer);
         this->writeInstruction(SpvOpName, id, var->fName.c_str(), fNameBuffer);
-        if (varDecl.fValue) {
-            SpvId value = this->writeExpression(*varDecl.fValue, out);
+        if (varDecl->fValue) {
+            SpvId value = this->writeExpression(*varDecl->fValue, out);
             this->writeInstruction(SpvOpStore, id, value, out);
         }
     }
@@ -2642,6 +2642,8 @@
 
 void SPIRVCodeGenerator::writeStatement(const Statement& s, OutputStream& out) {
     switch (s.fKind) {
+        case Statement::kNop_Kind:
+            break;
         case Statement::kBlock_Kind:
             this->writeBlock((Block&) s, out);
             break;
diff --git a/src/sksl/SkSLToken.h b/src/sksl/SkSLToken.h
index 8732e80..874c7eb 100644
--- a/src/sksl/SkSLToken.h
+++ b/src/sksl/SkSLToken.h
@@ -104,6 +104,7 @@
         COHERENT,
         VOLATILE,
         RESTRICT,
+        HASSIDEEFFECTS,
         STRUCT,
         LAYOUT,
         DIRECTIVE,
diff --git a/src/sksl/ast/SkSLASTFunction.h b/src/sksl/ast/SkSLASTFunction.h
index d9f3067..0dff3ae 100644
--- a/src/sksl/ast/SkSLASTFunction.h
+++ b/src/sksl/ast/SkSLASTFunction.h
@@ -19,10 +19,11 @@
  * A function declaration or definition. The fBody field will be null for declarations.
  */
 struct ASTFunction : public ASTDeclaration {
-    ASTFunction(Position position, std::unique_ptr<ASTType> returnType, String name,
-                std::vector<std::unique_ptr<ASTParameter>> parameters,
+    ASTFunction(Position position, Modifiers modifiers,  std::unique_ptr<ASTType> returnType,
+                String name, std::vector<std::unique_ptr<ASTParameter>> parameters, 
                 std::unique_ptr<ASTBlock> body)
     : INHERITED(position, kFunction_Kind)
+    , fModifiers(modifiers)
     , fReturnType(std::move(returnType))
     , fName(std::move(name))
     , fParameters(std::move(parameters))
@@ -44,6 +45,7 @@
         return result;
     }
 
+    const Modifiers fModifiers;
     const std::unique_ptr<ASTType> fReturnType;
     const String fName;
     const std::vector<std::unique_ptr<ASTParameter>> fParameters;
diff --git a/src/sksl/ir/SkSLBinaryExpression.h b/src/sksl/ir/SkSLBinaryExpression.h
index 73b1829..789db57 100644
--- a/src/sksl/ir/SkSLBinaryExpression.h
+++ b/src/sksl/ir/SkSLBinaryExpression.h
@@ -26,15 +26,19 @@
     , fOperator(op)
     , fRight(std::move(right)) {}
 
-    virtual std::unique_ptr<Expression> constantPropagate(
-                                                        const IRGenerator& irGenerator,
-                                                        const DefinitionMap& definitions) override {
+    std::unique_ptr<Expression> constantPropagate(const IRGenerator& irGenerator,
+                                                  const DefinitionMap& definitions) override {
         return irGenerator.constantFold(*fLeft,
                                         fOperator,
                                         *fRight);
     }
 
-    virtual String description() const override {
+    bool hasSideEffects() const override {
+        return Token::IsAssignment(fOperator) || fLeft->hasSideEffects() ||
+               fRight->hasSideEffects();
+    }
+
+    String description() const override {
         return "(" + fLeft->description() + " " + Token::OperatorName(fOperator) + " " +
                fRight->description() + ")";
     }
diff --git a/src/sksl/ir/SkSLBlock.h b/src/sksl/ir/SkSLBlock.h
index fe58d44..1197666 100644
--- a/src/sksl/ir/SkSLBlock.h
+++ b/src/sksl/ir/SkSLBlock.h
@@ -23,6 +23,15 @@
     , fSymbols(std::move(symbols))
     , fStatements(std::move(statements)) {}
 
+    bool isEmpty() const override {
+        for (const auto& s : fStatements) {
+            if (!s->isEmpty()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     String description() const override {
         String result("{");
         for (size_t i = 0; i < fStatements.size(); i++) {
@@ -36,7 +45,7 @@
     // it's important to keep fStatements defined after (and thus destroyed before) fSymbols,
     // because destroying statements can modify reference counts in symbols
     const std::shared_ptr<SymbolTable> fSymbols;
-    const std::vector<std::unique_ptr<Statement>> fStatements;
+    std::vector<std::unique_ptr<Statement>> fStatements;
 
     typedef Statement INHERITED;
 };
diff --git a/src/sksl/ir/SkSLBoolLiteral.h b/src/sksl/ir/SkSLBoolLiteral.h
index 4e1e050..13203a4 100644
--- a/src/sksl/ir/SkSLBoolLiteral.h
+++ b/src/sksl/ir/SkSLBoolLiteral.h
@@ -25,6 +25,10 @@
         return String(fValue ? "true" : "false");
     }
 
+    bool hasSideEffects() const override {
+        return false;
+    }
+
     bool isConstant() const override {
         return true;
     }
diff --git a/src/sksl/ir/SkSLConstructor.h b/src/sksl/ir/SkSLConstructor.h
index 5c647c7..208031a 100644
--- a/src/sksl/ir/SkSLConstructor.h
+++ b/src/sksl/ir/SkSLConstructor.h
@@ -30,20 +30,36 @@
     : INHERITED(position, kConstructor_Kind, type)
     , fArguments(std::move(arguments)) {}
 
-    virtual std::unique_ptr<Expression> constantPropagate(
-                                                        const IRGenerator& irGenerator,
-                                                        const DefinitionMap& definitions) override {
-        if (fArguments.size() == 1 && fArguments[0]->fKind == Expression::kIntLiteral_Kind &&
-            // promote float(1) to 1.0
-            fType == *irGenerator.fContext.fFloat_Type) {
-            int64_t intValue = ((IntLiteral&) *fArguments[0]).fValue;
-            return std::unique_ptr<Expression>(new FloatLiteral(irGenerator.fContext,
-                                                                fPosition,
-                                                                intValue));
+    std::unique_ptr<Expression> constantPropagate(const IRGenerator& irGenerator,
+                                                  const DefinitionMap& definitions) override {
+        if (fArguments.size() == 1 && fArguments[0]->fKind == Expression::kIntLiteral_Kind) {
+            if (fType == *irGenerator.fContext.fFloat_Type) {
+                // promote float(1) to 1.0
+                int64_t intValue = ((IntLiteral&) *fArguments[0]).fValue;
+                return std::unique_ptr<Expression>(new FloatLiteral(irGenerator.fContext,
+                                                                    fPosition,
+                                                                    intValue));
+            } else if (fType == *irGenerator.fContext.fUInt_Type) {
+                // promote uint(1) to 1u
+                int64_t intValue = ((IntLiteral&) *fArguments[0]).fValue;
+                return std::unique_ptr<Expression>(new IntLiteral(irGenerator.fContext,
+                                                                  fPosition,
+                                                                  intValue,
+                                                                  &fType));
+            }
         }
         return nullptr;
     }
 
+    bool hasSideEffects() const override {
+        for (const auto& arg : fArguments) {
+            if (arg->hasSideEffects()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     String description() const override {
         String result = fType.description() + "(";
         String separator;
@@ -65,6 +81,43 @@
         return true;
     }
 
+    const Expression& getVecComponent(int index) const {
+        ASSERT(fType.kind() == Type::kVector_Kind);
+        if (fArguments.size() == 1 && fArguments[0]->fType.kind() == Type::kScalar_Kind) {
+            return *fArguments[0];
+        }
+        int current = 0;
+        for (const auto& arg : fArguments) {
+            ASSERT(current <= index);
+            if (arg->fType.kind() == Type::kScalar_Kind) {
+                if (index == current) {
+                    return *arg;
+                }
+                current++;
+            } else {
+                ASSERT(arg->fType.kind() == Type::kVector_Kind);
+                ASSERT(arg->fKind == Expression::kConstructor_Kind);
+                if (current + arg->fType.columns() > index) {
+                    return ((const Constructor&) *arg).getVecComponent(index - current);
+                }
+                current += arg->fType.columns();
+            }
+        }
+        ABORT("failed to find vector component %d in %s\n", index, description().c_str());
+    }
+
+    double getFVecComponent(int index) const {
+        const Expression& c = this->getVecComponent(index);
+        ASSERT(c.fKind == Expression::kFloatLiteral_Kind);
+        return ((FloatLiteral&) c).fValue;
+    }
+
+    int64_t getIVecComponent(int index) const {
+        const Expression& c = this->getVecComponent(index);
+        ASSERT(c.fKind == Expression::kIntLiteral_Kind);
+        return ((IntLiteral&) c).fValue;
+    }
+
     std::vector<std::unique_ptr<Expression>> fArguments;
 
     typedef Expression INHERITED;
diff --git a/src/sksl/ir/SkSLDoStatement.h b/src/sksl/ir/SkSLDoStatement.h
index 4d3d348..f1ecd9a 100644
--- a/src/sksl/ir/SkSLDoStatement.h
+++ b/src/sksl/ir/SkSLDoStatement.h
@@ -27,7 +27,7 @@
         return "do " + fStatement->description() + " while (" + fTest->description() + ");";
     }
 
-    const std::unique_ptr<Statement> fStatement;
+    std::unique_ptr<Statement> fStatement;
     std::unique_ptr<Expression> fTest;
 
     typedef Statement INHERITED;
diff --git a/src/sksl/ir/SkSLExpression.h b/src/sksl/ir/SkSLExpression.h
index f87d810..5db9ddf 100644
--- a/src/sksl/ir/SkSLExpression.h
+++ b/src/sksl/ir/SkSLExpression.h
@@ -53,6 +53,13 @@
     }
 
     /**
+     * Returns true if evaluating the expression potentially has side effects. Expressions may never
+     * return false if they actually have side effects, but it is legal (though suboptimal) to
+     * return true if there are not actually any side effects.
+     */
+    virtual bool hasSideEffects() const = 0;
+
+    /**
      * Given a map of known constant variable values, substitute them in for references to those
      * variables occurring in this expression and its subexpressions.  Similar simplifications, such
      * as folding a constant binary expression down to a single value, may also be performed.
diff --git a/src/sksl/ir/SkSLFieldAccess.h b/src/sksl/ir/SkSLFieldAccess.h
index eead41c..e0a335f 100644
--- a/src/sksl/ir/SkSLFieldAccess.h
+++ b/src/sksl/ir/SkSLFieldAccess.h
@@ -31,7 +31,11 @@
     , fFieldIndex(fieldIndex)
     , fOwnerKind(ownerKind) {}
 
-    virtual String description() const override {
+    bool hasSideEffects() const override {
+        return fBase->hasSideEffects();
+    }
+
+    String description() const override {
         return fBase->description() + "." + fBase->fType.fields()[fFieldIndex].fName;
     }
 
diff --git a/src/sksl/ir/SkSLFloatLiteral.h b/src/sksl/ir/SkSLFloatLiteral.h
index 5ed1235..8f83e28 100644
--- a/src/sksl/ir/SkSLFloatLiteral.h
+++ b/src/sksl/ir/SkSLFloatLiteral.h
@@ -17,14 +17,19 @@
  * A literal floating point number.
  */
 struct FloatLiteral : public Expression {
-    FloatLiteral(const Context& context, Position position, double value)
-    : INHERITED(position, kFloatLiteral_Kind, *context.fFloat_Type)
+    FloatLiteral(const Context& context, Position position, double value,
+                 const Type* type = nullptr)
+    : INHERITED(position, kFloatLiteral_Kind, type ? *type : *context.fFloat_Type)
     , fValue(value) {}
 
-    virtual String description() const override {
+    String description() const override {
         return to_string(fValue);
     }
 
+    bool hasSideEffects() const override {
+        return false;
+    }
+
     bool isConstant() const override {
         return true;
     }
diff --git a/src/sksl/ir/SkSLForStatement.h b/src/sksl/ir/SkSLForStatement.h
index b72c26b..ca3e6cf 100644
--- a/src/sksl/ir/SkSLForStatement.h
+++ b/src/sksl/ir/SkSLForStatement.h
@@ -48,10 +48,10 @@
     // it's important to keep fSymbols defined first (and thus destroyed last) because destroying
     // the other fields can update symbol reference counts
     const std::shared_ptr<SymbolTable> fSymbols;
-    const std::unique_ptr<Statement> fInitializer;
+    std::unique_ptr<Statement> fInitializer;
     std::unique_ptr<Expression> fTest;
     std::unique_ptr<Expression> fNext;
-    const std::unique_ptr<Statement> fStatement;
+    std::unique_ptr<Statement> fStatement;
 
     typedef Statement INHERITED;
 };
diff --git a/src/sksl/ir/SkSLFunctionCall.h b/src/sksl/ir/SkSLFunctionCall.h
index 1a5c6fd..44f8c7e 100644
--- a/src/sksl/ir/SkSLFunctionCall.h
+++ b/src/sksl/ir/SkSLFunctionCall.h
@@ -23,6 +23,15 @@
     , fFunction(std::move(function))
     , fArguments(std::move(arguments)) {}
 
+    bool hasSideEffects() const override {
+        for (const auto& arg : fArguments) {
+            if (arg->hasSideEffects()) {
+                return true;
+            }
+        }
+        return fFunction.fModifiers.fFlags & Modifiers::kHasSideEffects_Flag;
+    }
+
     String description() const override {
         String result = fFunction.fName + "(";
         String separator;
diff --git a/src/sksl/ir/SkSLFunctionDeclaration.h b/src/sksl/ir/SkSLFunctionDeclaration.h
index 05ba03a..8704c9a 100644
--- a/src/sksl/ir/SkSLFunctionDeclaration.h
+++ b/src/sksl/ir/SkSLFunctionDeclaration.h
@@ -21,11 +21,12 @@
  * A function declaration (not a definition -- does not contain a body).
  */
 struct FunctionDeclaration : public Symbol {
-    FunctionDeclaration(Position position, String name,
+    FunctionDeclaration(Position position, Modifiers modifiers, String name,
                         std::vector<const Variable*> parameters, const Type& returnType)
     : INHERITED(position, kFunctionDeclaration_Kind, std::move(name))
     , fDefined(false)
     , fBuiltin(false)
+    , fModifiers(modifiers)
     , fParameters(std::move(parameters))
     , fReturnType(returnType) {}
 
@@ -102,6 +103,7 @@
 
     mutable bool fDefined;
     bool fBuiltin;
+    Modifiers fModifiers;
     const std::vector<const Variable*> fParameters;
     const Type& fReturnType;
 
diff --git a/src/sksl/ir/SkSLFunctionDefinition.h b/src/sksl/ir/SkSLFunctionDefinition.h
index e87ee63..0277db1 100644
--- a/src/sksl/ir/SkSLFunctionDefinition.h
+++ b/src/sksl/ir/SkSLFunctionDefinition.h
@@ -19,7 +19,7 @@
  */
 struct FunctionDefinition : public ProgramElement {
     FunctionDefinition(Position position, const FunctionDeclaration& declaration,
-                       std::unique_ptr<Block> body)
+                       std::unique_ptr<Statement> body)
     : INHERITED(position, kFunction_Kind)
     , fDeclaration(declaration)
     , fBody(std::move(body)) {}
@@ -29,7 +29,7 @@
     }
 
     const FunctionDeclaration& fDeclaration;
-    const std::unique_ptr<Block> fBody;
+    std::unique_ptr<Statement> fBody;
 
     typedef ProgramElement INHERITED;
 };
diff --git a/src/sksl/ir/SkSLFunctionReference.h b/src/sksl/ir/SkSLFunctionReference.h
index 49ddf83..ee761c2 100644
--- a/src/sksl/ir/SkSLFunctionReference.h
+++ b/src/sksl/ir/SkSLFunctionReference.h
@@ -24,7 +24,11 @@
     : INHERITED(position, kFunctionReference_Kind, *context.fInvalid_Type)
     , fFunctions(function) {}
 
-    virtual String description() const override {
+    bool hasSideEffects() const override {
+        return false;
+    }
+
+    String description() const override {
         ASSERT(false);
         return String("<function>");
     }
diff --git a/src/sksl/ir/SkSLIfStatement.h b/src/sksl/ir/SkSLIfStatement.h
index a7e0aad..0377b12 100644
--- a/src/sksl/ir/SkSLIfStatement.h
+++ b/src/sksl/ir/SkSLIfStatement.h
@@ -33,8 +33,9 @@
     }
 
     std::unique_ptr<Expression> fTest;
-    const std::unique_ptr<Statement> fIfTrue;
-    const std::unique_ptr<Statement> fIfFalse;
+    std::unique_ptr<Statement> fIfTrue;
+    // may be null
+    std::unique_ptr<Statement> fIfFalse;
 
     typedef Statement INHERITED;
 };
diff --git a/src/sksl/ir/SkSLIndexExpression.h b/src/sksl/ir/SkSLIndexExpression.h
index 68823bf..49633b6 100644
--- a/src/sksl/ir/SkSLIndexExpression.h
+++ b/src/sksl/ir/SkSLIndexExpression.h
@@ -51,6 +51,10 @@
         ASSERT(fIndex->fType == *context.fInt_Type || fIndex->fType == *context.fUInt_Type);
     }
 
+    bool hasSideEffects() const override {
+        return fBase->hasSideEffects() || fIndex->hasSideEffects();
+    }
+
     String description() const override {
         return fBase->description() + "[" + fIndex->description() + "]";
     }
diff --git a/src/sksl/ir/SkSLIntLiteral.h b/src/sksl/ir/SkSLIntLiteral.h
index 2322a3d..3a95ed6 100644
--- a/src/sksl/ir/SkSLIntLiteral.h
+++ b/src/sksl/ir/SkSLIntLiteral.h
@@ -23,11 +23,15 @@
     : INHERITED(position, kIntLiteral_Kind, type ? *type : *context.fInt_Type)
     , fValue(value) {}
 
-    virtual String description() const override {
+    String description() const override {
         return to_string(fValue);
     }
 
-   bool isConstant() const override {
+    bool hasSideEffects() const override {
+        return false;
+    }
+
+    bool isConstant() const override {
         return true;
     }
 
diff --git a/src/sksl/ir/SkSLModifiers.h b/src/sksl/ir/SkSLModifiers.h
index 9fae5b0..8aaf2ae 100644
--- a/src/sksl/ir/SkSLModifiers.h
+++ b/src/sksl/ir/SkSLModifiers.h
@@ -17,21 +17,22 @@
  */
 struct Modifiers {
     enum Flag {
-        kNo_Flag            =    0,
-        kConst_Flag         =    1,
-        kIn_Flag            =    2,
-        kOut_Flag           =    4,
-        kLowp_Flag          =    8,
-        kMediump_Flag       =   16,
-        kHighp_Flag         =   32,
-        kUniform_Flag       =   64,
-        kFlat_Flag          =  128,
-        kNoPerspective_Flag =  256,
-        kReadOnly_Flag      =  512,
-        kWriteOnly_Flag     = 1024,
-        kCoherent_Flag      = 2048,
-        kVolatile_Flag      = 4096,
-        kRestrict_Flag      = 8192
+        kNo_Flag             =       0,
+        kConst_Flag          = 1 <<  0,
+        kIn_Flag             = 1 <<  1,
+        kOut_Flag            = 1 <<  2,
+        kLowp_Flag           = 1 <<  3,
+        kMediump_Flag        = 1 <<  4,
+        kHighp_Flag          = 1 <<  5,
+        kUniform_Flag        = 1 <<  6,
+        kFlat_Flag           = 1 <<  7,
+        kNoPerspective_Flag  = 1 <<  8,
+        kReadOnly_Flag       = 1 <<  9,
+        kWriteOnly_Flag      = 1 << 10,
+        kCoherent_Flag       = 1 << 11,
+        kVolatile_Flag       = 1 << 12,
+        kRestrict_Flag       = 1 << 13,
+        kHasSideEffects_Flag = 1 << 14
     };
 
     Modifiers()
@@ -80,6 +81,9 @@
         if (fFlags & kRestrict_Flag) {
             result += "restrict ";
         }
+        if (fFlags & kHasSideEffects_Flag) {
+            result += "sk_has_side_effects ";
+        }
 
         if ((fFlags & kIn_Flag) && (fFlags & kOut_Flag)) {
             result += "inout ";
diff --git a/src/sksl/ir/SkSLNop.h b/src/sksl/ir/SkSLNop.h
new file mode 100644
index 0000000..5ebea40
--- /dev/null
+++ b/src/sksl/ir/SkSLNop.h
@@ -0,0 +1,36 @@
+/*
+ * 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_NOP
+#define SKSL_NOP
+
+#include "SkSLStatement.h"
+#include "SkSLSymbolTable.h"
+
+namespace SkSL {
+
+/**
+ * A no-op statement that does nothing.
+ */
+struct Nop : public Statement {
+    Nop()
+    : INHERITED(Position(), kNop_Kind) {}
+
+    virtual bool isEmpty() const override {
+        return true;
+    }
+
+    String description() const override {
+        return String(";");
+    }
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLPostfixExpression.h b/src/sksl/ir/SkSLPostfixExpression.h
index 2c84af7..e02555d 100644
--- a/src/sksl/ir/SkSLPostfixExpression.h
+++ b/src/sksl/ir/SkSLPostfixExpression.h
@@ -22,7 +22,11 @@
     , fOperand(std::move(operand))
     , fOperator(op) {}
 
-    virtual String description() const override {
+    bool hasSideEffects() const override {
+        return true;
+    }
+
+    String description() const override {
         return fOperand->description() + Token::OperatorName(fOperator);
     }
 
diff --git a/src/sksl/ir/SkSLPrefixExpression.h b/src/sksl/ir/SkSLPrefixExpression.h
index 4fa54ca..a61ff65 100644
--- a/src/sksl/ir/SkSLPrefixExpression.h
+++ b/src/sksl/ir/SkSLPrefixExpression.h
@@ -22,7 +22,29 @@
     , fOperand(std::move(operand))
     , fOperator(op) {}
 
-    virtual String description() const override {
+    bool isConstant() const override {
+        return fOperator == Token::MINUS && fOperand->isConstant();
+    }
+
+    bool hasSideEffects() const override {
+        return fOperator == Token::PLUSPLUS || fOperator == Token::MINUSMINUS ||
+               fOperand->hasSideEffects();
+    }
+
+    virtual std::unique_ptr<Expression> constantPropagate(
+                                                        const IRGenerator& irGenerator,
+                                                        const DefinitionMap& definitions) override {
+        if (fOperand->fKind == Expression::kFloatLiteral_Kind) {
+            return std::unique_ptr<Expression>(new FloatLiteral(
+                                                              irGenerator.fContext,
+                                                              Position(),
+                                                              -((FloatLiteral&) *fOperand).fValue));
+
+        }
+        return nullptr;
+    }
+
+    String description() const override {
         return Token::OperatorName(fOperator) + fOperand->description();
     }
 
diff --git a/src/sksl/ir/SkSLStatement.h b/src/sksl/ir/SkSLStatement.h
index ba1a087..9af18ec 100644
--- a/src/sksl/ir/SkSLStatement.h
+++ b/src/sksl/ir/SkSLStatement.h
@@ -25,7 +25,9 @@
         kDo_Kind,
         kExpression_Kind,
         kFor_Kind,
+        kGroup_Kind,
         kIf_Kind,
+        kNop_Kind,
         kReturn_Kind,
         kSwitch_Kind,
         kVarDeclarations_Kind,
@@ -36,6 +38,10 @@
     : INHERITED(position)
     , fKind(kind) {}
 
+    virtual bool isEmpty() const {
+        return false;
+    }
+
     const Kind fKind;
 
     typedef IRNode INHERITED;
diff --git a/src/sksl/ir/SkSLSwizzle.h b/src/sksl/ir/SkSLSwizzle.h
index 1725ec2..dedb81c 100644
--- a/src/sksl/ir/SkSLSwizzle.h
+++ b/src/sksl/ir/SkSLSwizzle.h
@@ -69,6 +69,34 @@
         ASSERT(fComponents.size() >= 1 && fComponents.size() <= 4);
     }
 
+    virtual std::unique_ptr<Expression> constantPropagate(
+                                                        const IRGenerator& irGenerator,
+                                                        const DefinitionMap& definitions) override {
+
+        if (fBase->fKind == Expression::kConstructor_Kind && fBase->isConstant()) {
+            // we're swizzling a constant vector, e.g. vec4(1).x. Simplify it.
+            ASSERT(fBase->fKind == Expression::kConstructor_Kind);
+            if (fType == *irGenerator.fContext.fInt_Type) {
+                ASSERT(fComponents.size() == 1);
+                int64_t value = ((Constructor&) *fBase).getIVecComponent(fComponents[0]);
+                return std::unique_ptr<Expression>(new IntLiteral(irGenerator.fContext,
+                                                                    Position(),
+                                                                    value));
+            } else if (fType == *irGenerator.fContext.fFloat_Type) {
+                ASSERT(fComponents.size() == 1);
+                double value = ((Constructor&) *fBase).getFVecComponent(fComponents[0]);
+                return std::unique_ptr<Expression>(new FloatLiteral(irGenerator.fContext,
+                                                                    Position(),
+                                                                    value));
+            }
+        }
+        return nullptr;
+    }
+
+    bool hasSideEffects() const override {
+        return fBase->hasSideEffects();
+    }
+
     String description() const override {
         String result = fBase->description() + ".";
         for (int x : fComponents) {
diff --git a/src/sksl/ir/SkSLTernaryExpression.h b/src/sksl/ir/SkSLTernaryExpression.h
index 9fbac19..567af56 100644
--- a/src/sksl/ir/SkSLTernaryExpression.h
+++ b/src/sksl/ir/SkSLTernaryExpression.h
@@ -26,6 +26,10 @@
         ASSERT(fIfTrue->fType == fIfFalse->fType);
     }
 
+    bool hasSideEffects() const override {
+        return fTest->hasSideEffects() || fIfTrue->hasSideEffects() || fIfFalse->hasSideEffects();
+    }
+
     String description() const override {
         return "(" + fTest->description() + " ? " + fIfTrue->description() + " : " +
                fIfFalse->description() + ")";
diff --git a/src/sksl/ir/SkSLTypeReference.h b/src/sksl/ir/SkSLTypeReference.h
index b12c185..eae1989 100644
--- a/src/sksl/ir/SkSLTypeReference.h
+++ b/src/sksl/ir/SkSLTypeReference.h
@@ -22,6 +22,10 @@
     : INHERITED(position, kTypeReference_Kind, *context.fInvalid_Type)
     , fValue(type) {}
 
+    bool hasSideEffects() const override {
+        return false;
+    }
+
     String description() const override {
         return fValue.name();
     }
diff --git a/src/sksl/ir/SkSLUnresolvedFunction.h b/src/sksl/ir/SkSLUnresolvedFunction.h
index c5fdbd0..b222bc3 100644
--- a/src/sksl/ir/SkSLUnresolvedFunction.h
+++ b/src/sksl/ir/SkSLUnresolvedFunction.h
@@ -26,7 +26,7 @@
 #endif
     }
 
-    virtual String description() const override {
+    String description() const override {
         return fName;
     }
 
diff --git a/src/sksl/ir/SkSLVarDeclarations.h b/src/sksl/ir/SkSLVarDeclarations.h
index 5a006bd..dbf2928 100644
--- a/src/sksl/ir/SkSLVarDeclarations.h
+++ b/src/sksl/ir/SkSLVarDeclarations.h
@@ -52,8 +52,8 @@
  * A variable declaration statement, which may consist of one or more individual variables.
  */
 struct VarDeclarations : public ProgramElement {
-    VarDeclarations(Position position, const Type* baseType,
-                    std::vector<VarDeclaration> vars)
+    VarDeclarations(Position position, const Type* baseType, 
+                    std::vector<std::unique_ptr<VarDeclaration>> vars)
     : INHERITED(position, kVar_Kind)
     , fBaseType(*baseType)
     , fVars(std::move(vars)) {}
@@ -62,18 +62,18 @@
         if (!fVars.size()) {
             return String();
         }
-        String result = fVars[0].fVar->fModifiers.description() + fBaseType.description() + " ";
+        String result = fVars[0]->fVar->fModifiers.description() + fBaseType.description() + " ";
         String separator;
         for (const auto& var : fVars) {
             result += separator;
             separator = ", ";
-            result += var.description();
+            result += var->description();
         }
         return result;
     }
 
     const Type& fBaseType;
-    std::vector<VarDeclaration> fVars;
+    std::vector<std::unique_ptr<VarDeclaration>> fVars;
 
     typedef ProgramElement INHERITED;
 };
diff --git a/src/sksl/ir/SkSLVariable.h b/src/sksl/ir/SkSLVariable.h
index 21f17ba..05bba20 100644
--- a/src/sksl/ir/SkSLVariable.h
+++ b/src/sksl/ir/SkSLVariable.h
@@ -40,6 +40,10 @@
         return fModifiers.description() + fType.fName + " " + fName;
     }
 
+    bool dead() const {
+        return !fWriteCount || (!fReadCount && !(fModifiers.fFlags & Modifiers::kOut_Flag));
+    }
+
     mutable Modifiers fModifiers;
     const Type& fType;
     const Storage fStorage;
diff --git a/src/sksl/ir/SkSLVariableReference.h b/src/sksl/ir/SkSLVariableReference.h
index af181f8..3b8ebe3 100644
--- a/src/sksl/ir/SkSLVariableReference.h
+++ b/src/sksl/ir/SkSLVariableReference.h
@@ -41,7 +41,7 @@
         }
     }
 
-    virtual ~VariableReference() override {
+    ~VariableReference() override {
         if (fRefKind != kWrite_RefKind) {
             fVariable.fReadCount--;
         }
@@ -67,39 +67,63 @@
         fRefKind = refKind;
     }
 
+    bool hasSideEffects() const override {
+        return false;
+    }
+
     String description() const override {
         return fVariable.fName;
     }
 
-    virtual std::unique_ptr<Expression> constantPropagate(
-                                                        const IRGenerator& irGenerator,
-                                                        const DefinitionMap& definitions) override {
-        auto exprIter = definitions.find(&fVariable);
-        if (exprIter != definitions.end() && exprIter->second) {
-            const Expression* expr = exprIter->second->get();
-            switch (expr->fKind) {
-                case Expression::kIntLiteral_Kind:
-                    return std::unique_ptr<Expression>(new IntLiteral(
-                                                                     irGenerator.fContext,
-                                                                     Position(),
-                                                                     ((IntLiteral*) expr)->fValue));
-                case Expression::kFloatLiteral_Kind:
-                    return std::unique_ptr<Expression>(new FloatLiteral(
-                                                                   irGenerator.fContext,
+    static std::unique_ptr<Expression> copy_constant(const IRGenerator& irGenerator,
+                                                     const Expression* expr) {
+        ASSERT(expr->isConstant());
+        switch (expr->fKind) {
+            case Expression::kIntLiteral_Kind:
+                return std::unique_ptr<Expression>(new IntLiteral(
+                                                                 irGenerator.fContext,
+                                                                 Position(),
+                                                                 ((IntLiteral*) expr)->fValue));
+            case Expression::kFloatLiteral_Kind:
+                return std::unique_ptr<Expression>(new FloatLiteral(
+                                                               irGenerator.fContext,
+                                                               Position(),
+                                                               ((FloatLiteral*) expr)->fValue));
+            case Expression::kBoolLiteral_Kind:
+                return std::unique_ptr<Expression>(new BoolLiteral(irGenerator.fContext,
                                                                    Position(),
-                                                                   ((FloatLiteral*) expr)->fValue));
-                default:
-                    break;
+                                                                   ((BoolLiteral*) expr)->fValue));
+            case Expression::kConstructor_Kind: {
+                const Constructor* c = (const Constructor*) expr;
+                std::vector<std::unique_ptr<Expression>> args;
+                for (const auto& arg : c->fArguments) {
+                    args.push_back(copy_constant(irGenerator, arg.get()));
+                }
+                return std::unique_ptr<Expression>(new Constructor(Position(), c->fType,
+                                                                   std::move(args)));
             }
+            default:
+                ABORT("unsupported constant\n");
+        }
+    }
+
+    std::unique_ptr<Expression> constantPropagate(const IRGenerator& irGenerator,
+                                                  const DefinitionMap& definitions) override {
+        if (fRefKind != kRead_RefKind) {
+            return nullptr;
+        }
+        auto exprIter = definitions.find(&fVariable);
+        if (exprIter != definitions.end() && exprIter->second &&
+            (*exprIter->second)->isConstant()) {
+            return copy_constant(irGenerator, exprIter->second->get());
         }
         return nullptr;
     }
 
     const Variable& fVariable;
-
-private:
     RefKind fRefKind;
 
+private:
     typedef Expression INHERITED;
 };
 
diff --git a/src/sksl/ir/SkSLWhileStatement.h b/src/sksl/ir/SkSLWhileStatement.h
index c35d6df..6df1619 100644
--- a/src/sksl/ir/SkSLWhileStatement.h
+++ b/src/sksl/ir/SkSLWhileStatement.h
@@ -28,7 +28,7 @@
     }
 
     std::unique_ptr<Expression> fTest;
-    const std::unique_ptr<Statement> fStatement;
+    std::unique_ptr<Statement> fStatement;
 
     typedef Statement INHERITED;
 };
diff --git a/src/sksl/lex.sksl.c b/src/sksl/lex.sksl.c
index ad08251..3ad9034 100644
--- a/src/sksl/lex.sksl.c
+++ b/src/sksl/lex.sksl.c
@@ -424,8 +424,8 @@
 	yyg->yy_hold_char = *yy_cp; \
 	*yy_cp = '\0'; \
 	yyg->yy_c_buf_p = yy_cp;
-#define YY_NUM_RULES 92
-#define YY_END_OF_BUFFER 93
+#define YY_NUM_RULES 93
+#define YY_END_OF_BUFFER 94
 /* This struct is not used in this scanner,
    but its presence is necessary. */
 struct yy_trans_info
@@ -433,36 +433,37 @@
 	flex_int32_t yy_verify;
 	flex_int32_t yy_nxt;
 	};
-static const flex_int16_t yy_accept[253] =
+static const flex_int16_t yy_accept[271] =
     {   0,
-        0,    0,   93,   91,   90,   90,   64,   91,   38,   54,
-       59,   40,   41,   52,   50,   47,   51,   46,   53,    4,
-        4,   66,   87,   71,   67,   70,   65,   44,   45,   58,
-       38,   38,   38,   38,   38,   38,   38,   38,   38,   38,
-       38,   38,   38,   38,   38,   38,   38,   38,   42,   57,
-       43,   60,   90,   69,   39,   38,   78,   63,   83,   76,
-       48,   74,   49,   75,    1,    0,   88,   77,    2,    4,
-        0,    0,   55,   73,   68,   72,   56,   82,   62,   38,
-       38,   38,   38,   38,   12,   38,   38,   38,   38,   38,
-        8,   20,   38,   38,   38,   38,   38,   38,   38,   38,
+        0,    0,   94,   92,   91,   91,   65,   92,   39,   55,
+       60,   41,   42,   53,   51,   48,   52,   47,   54,    4,
+        4,   67,   88,   72,   68,   71,   66,   45,   46,   59,
+       39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
+       39,   39,   39,   39,   39,   39,   39,   39,   43,   58,
+       44,   61,   91,   70,   40,   39,   79,   64,   84,   77,
+       49,   75,   50,   76,    1,    0,   89,   78,    2,    4,
+        0,    0,   56,   74,   69,   73,   57,   83,   63,   39,
+       39,   39,   39,   39,   12,   39,   39,   39,   39,   39,
+        8,   20,   39,   39,   39,   39,   39,   39,   39,   39,
 
-       38,   38,   38,   38,   38,   38,   81,   61,   39,   86,
-        0,    0,    0,   88,    1,    0,    0,    3,    5,   79,
-       80,   85,   38,   38,   38,   38,   38,   38,   38,   38,
-       38,   10,   38,   38,   38,   38,   38,   38,   21,   38,
-       38,   38,   38,   38,   38,   38,   38,   38,   38,   38,
-       84,    0,    1,   89,    0,    0,    2,   38,   14,   38,
-       38,   38,   38,   38,    9,   38,   28,   38,   38,   38,
-       25,   38,   38,   38,   38,   38,   38,   38,   38,    6,
-       38,   38,   38,   38,    0,    1,   16,   38,   24,   38,
-       38,   38,    7,   27,   22,   38,   38,   38,   38,   38,
+       39,   39,   39,   39,   39,   39,   39,   82,   62,   40,
+       87,    0,    0,    0,   89,    1,    0,    0,    3,    5,
+       80,   81,   86,   39,   39,   39,   39,   39,   39,   39,
+       39,   39,   10,   39,   39,   39,   39,   39,   39,   21,
+       39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
+       39,   39,   85,    0,    1,   90,    0,    0,    2,   39,
+       14,   39,   39,   39,   39,   39,    9,   39,   28,   39,
+       39,   39,   25,   39,   39,   39,   39,   39,   39,   39,
+       39,   39,    6,   39,   39,   39,   39,    0,    1,   16,
+       39,   24,   39,   39,   39,    7,   27,   22,   39,   39,
 
-       38,   38,   38,   38,   38,   38,   11,   38,   38,   38,
-       38,   38,   36,   38,   38,   38,   38,   38,   19,   35,
-       13,   38,   38,   38,   38,   38,   15,   18,   26,   38,
-       38,   38,   38,   23,   38,   38,   32,   17,   38,   38,
-       30,   34,   33,   38,   38,   37,   31,   38,   38,   38,
-       29,    0
+       39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
+       11,   39,   39,   39,   39,   39,   37,   39,   39,   39,
+       39,   39,   19,   39,   36,   13,   39,   39,   39,   39,
+       39,   15,   18,   26,   39,   39,   39,   39,   39,   23,
+       39,   39,   32,   17,   39,   39,   30,   34,   39,   33,
+       39,   39,   38,   39,   31,   39,   39,   39,   39,   39,
+       39,   29,   39,   39,   39,   39,   39,   39,   35,    0
     } ;
 
 static const YY_CHAR yy_ec[256] =
@@ -476,11 +477,11 @@
        22,   23,   24,    1,   25,   25,   25,   25,   26,   25,
         6,    6,    6,    6,    6,    6,    6,    6,    6,    6,
         6,    6,    6,    6,    6,    6,    6,    6,    6,    6,
-       27,    1,   28,   29,    6,    1,   30,   31,   32,   33,
+       27,    1,   28,   29,   30,    1,   31,   32,   33,   34,
 
-       34,   35,   36,   37,   38,    6,   39,   40,   41,   42,
-       43,   44,    6,   45,   46,   47,   48,   49,   50,   51,
-       52,    6,   53,   54,   55,   56,    1,    1,    1,    1,
+       35,   36,   37,   38,   39,    6,   40,   41,   42,   43,
+       44,   45,    6,   46,   47,   48,   49,   50,   51,   52,
+       53,    6,   54,   55,   56,   57,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
@@ -497,178 +498,188 @@
         1,    1,    1,    1,    1
     } ;
 
-static const YY_CHAR yy_meta[57] =
+static const YY_CHAR yy_meta[58] =
     {   0,
         1,    1,    2,    1,    1,    3,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    4,    4,    1,    1,
-        1,    1,    1,    1,    5,    5,    1,    1,    1,    5,
-        5,    5,    5,    5,    5,    3,    3,    3,    3,    3,
+        1,    1,    1,    1,    5,    5,    1,    1,    1,    3,
+        5,    5,    5,    5,    5,    5,    3,    3,    3,    3,
         3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
-        3,    3,    1,    1,    1,    1
+        3,    3,    3,    1,    1,    1,    1
     } ;
 
-static const flex_int16_t yy_base[259] =
+static const flex_int16_t yy_base[277] =
     {   0,
-        0,    0,  320,  321,   55,   57,  297,    0,    0,  296,
-       53,  321,  321,  295,   50,  321,   49,   47,   57,   52,
-       59,  321,  321,   59,  294,   60,  321,  321,  321,   62,
-      270,   57,   54,  274,   59,  275,   59,   65,  278,  268,
-      262,  264,  274,   57,  262,  264,  262,   53,  321,   74,
-      321,  321,  103,  321,    0,    0,  321,  282,  321,  321,
-      321,  321,  321,  321,   92,  292,    0,  321,   95,   99,
-      118,    0,  280,  321,  321,  321,  279,  321,  278,  265,
-      252,   78,  262,  250,    0,  249,  254,  263,  247,  255,
-        0,  247,  237,  238,  254,  242,  238,  250,   92,  238,
+        0,    0,  339,  340,   56,   58,  316,    0,    0,  315,
+       54,  340,  340,  314,   51,  340,   50,   48,   58,   53,
+       60,  340,  340,   60,  313,   61,  340,  340,  340,   63,
+      288,   56,   54,  292,   60,  293,   54,   63,  296,  286,
+      280,  282,  292,   62,  280,  282,  280,   65,  340,   74,
+      340,  340,  106,  340,    0,    0,  340,  301,  340,  340,
+      340,  340,  340,  340,   97,  311,    0,  340,   99,  104,
+      119,    0,  299,  340,  340,  340,  298,  340,  297,  283,
+      270,   97,  280,  268,    0,  267,  272,  281,  265,  273,
+        0,  265,  255,  256,  272,  260,  256,  268,   95,  272,
 
-      244,  233,  242,  239,  240,  239,  321,  254,    0,  321,
-      128,  264,  258,    0,  126,  136,  106,  138,    0,  321,
-      321,  321,  243,  238,  237,  111,  240,  237,  234,  221,
-      219,    0,  228,  216,  220,  218,  223,  226,    0,  227,
-      225,  210,  208,  207,  207,  219,  217,  221,  210,  202,
-      321,  144,  146,  321,  153,  151,  155,  209,    0,  202,
-      199,  207,  196,  213,    0,  208,    0,  197,  193,  191,
-        0,  190,  192,  198,  192,  189,  188,  200,  199,    0,
-      187,  182,  194,  193,  157,  159,    0,  192,    0,  183,
-      184,  178,    0,    0,    0,  175,  180,  174,  173,  176,
+      255,  261,  250,  259,  256,  257,  256,  340,  272,    0,
+      340,  132,  282,  276,    0,  130,  140,  110,  142,    0,
+      340,  340,  340,  260,  255,  254,  114,  257,  254,  251,
+      238,  236,    0,  245,  233,  237,  235,  240,  243,    0,
+      244,  242,  227,  225,  235,  223,  223,  235,  233,  237,
+      226,  218,  340,  146,  149,  340,  156,  154,  158,  225,
+        0,  218,  215,  223,  212,  229,    0,  224,    0,  213,
+      209,  207,    0,  206,  208,  214,  208,  205,  204,  218,
+      215,  214,    0,  202,  197,  209,  208,  160,  162,    0,
+      207,    0,  198,  199,  193,    0,    0,    0,  190,  195,
 
-      179,  174,  168,  177,  168,  174,    0,  168,  168,  161,
-      161,  174,    0,  162,  161,  166,  163,  170,    0,    0,
-        0,  160,  160,  157,  146,  145,    0,    0,    0,  132,
-      116,   99,  102,    0,  113,  101,    0,    0,  105,   92,
-        0,    0,    0,   79,   80,    0,    0,   81,   62,   32,
-        0,  321,  175,  178,  181,  186,  191,  193
+      189,  188,  191,  194,  189,  184,  182,  191,  182,  188,
+        0,  182,  182,  175,  175,  188,    0,  176,  175,  180,
+      177,  184,    0,  186,    0,    0,  173,  173,  170,  164,
+      176,    0,    0,    0,  175,  165,  155,  159,  159,    0,
+      170,  163,    0,    0,  170,  159,    0,    0,  157,    0,
+      129,  121,    0,  121,    0,  114,  116,   95,  111,  103,
+       89,    0,   84,   82,   77,   73,   51,   20,    0,  340,
+      178,  181,  184,  189,  194,  196
     } ;
 
-static const flex_int16_t yy_def[259] =
+static const flex_int16_t yy_def[277] =
     {   0,
-      252,    1,  252,  252,  252,  252,  252,  253,  254,  252,
-      252,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  252,  252,
-      252,  252,  252,  252,  255,  254,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  256,  257,  252,  252,  252,
-      252,  258,  252,  252,  252,  252,  252,  252,  252,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
+      270,    1,  270,  270,  270,  270,  270,  271,  272,  270,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  270,  270,
+      270,  270,  270,  270,  273,  272,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  274,  275,  270,  270,  270,
+      270,  276,  270,  270,  270,  270,  270,  270,  270,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
 
-      254,  254,  254,  254,  254,  254,  252,  252,  255,  252,
-      252,  256,  256,  257,  252,  252,  252,  252,  258,  252,
-      252,  252,  254,  254,  254,  254,  254,  254,  254,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
-      252,  252,  252,  252,  252,  252,  252,  254,  254,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
-      254,  254,  254,  254,  252,  252,  254,  254,  254,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
+      272,  272,  272,  272,  272,  272,  272,  270,  270,  273,
+      270,  270,  274,  274,  275,  270,  270,  270,  270,  276,
+      270,  270,  270,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  270,  270,  270,  270,  270,  270,  270,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  270,  270,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
 
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
-      254,  254,  254,  254,  254,  254,  254,  254,  254,  254,
-      254,    0,  252,  252,  252,  252,  252,  252
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,  272,
+      272,  272,  272,  272,  272,  272,  272,  272,  272,    0,
+      270,  270,  270,  270,  270,  270
     } ;
 
-static const flex_int16_t yy_nxt[378] =
+static const flex_int16_t yy_nxt[398] =
     {   0,
         4,    5,    6,    7,    8,    9,   10,   11,   12,   13,
        14,   15,   16,   17,   18,   19,   20,   21,   22,   23,
        24,   25,   26,   27,    9,    9,   28,   29,   30,    9,
-       31,   32,   33,   34,   35,    9,   36,   37,    9,   38,
-       39,   40,   41,   42,   43,   44,   45,   46,   47,   48,
-        9,    9,   49,   50,   51,   52,   53,   53,   53,   53,
-       58,   61,   63,   65,   65,  251,   69,   66,   70,   70,
-       64,   62,   67,   69,   59,   70,   70,   71,   68,   73,
-       74,   76,   77,   78,   71,   71,   81,   83,   87,  105,
-       79,   84,   71,   91,   93,  107,   85,  106,   88,   82,
+        9,   31,   32,   33,   34,   35,    9,   36,   37,    9,
+       38,   39,   40,   41,   42,   43,   44,   45,   46,   47,
+       48,    9,    9,   49,   50,   51,   52,   53,   53,   53,
+       53,   58,   61,   63,   65,   65,  269,   69,   66,   70,
+       70,   64,   62,   67,   69,   59,   70,   70,   71,   68,
+       73,   74,   76,   77,   78,   71,   81,   71,   83,   91,
+       87,   79,   84,   93,   71,  108,   92,   85,  268,   82,
 
-       92,   89,   72,  100,   53,   53,  101,   94,   65,   65,
-      250,  115,  115,   69,  125,   70,   70,  111,  249,  126,
-      116,  141,  118,  118,   71,  111,  248,  108,  116,  117,
-      247,  117,   71,  246,  118,  118,  245,  142,  143,  152,
-      244,  152,  115,  115,  153,  153,  243,  156,  242,  156,
-      241,  155,  157,  157,  118,  118,  161,  162,  240,  155,
-      153,  153,  153,  153,  185,  239,  185,  157,  157,  186,
-      186,  157,  157,  186,  186,  186,  186,   55,  238,   55,
-       56,   56,   56,  109,  109,  109,  112,  112,  112,  112,
-      112,  114,  237,  114,  114,  114,  119,  119,  236,  235,
+       88,  100,  106,   89,   72,  267,   94,   53,   53,  101,
+      107,  266,  102,   65,   65,  116,  116,  265,   69,  264,
+       70,   70,  112,  263,  117,  142,  119,  119,  109,   71,
+      118,  112,  118,  117,  126,  119,  119,  262,   71,  127,
+      261,  143,  144,  154,  260,  154,  116,  116,  155,  155,
+      259,  158,  258,  158,  257,  157,  159,  159,  119,  119,
+      163,  164,  155,  155,  157,  155,  155,  188,  256,  188,
+      159,  159,  189,  189,  159,  159,  189,  189,  189,  189,
+       55,  255,   55,   56,   56,   56,  110,  110,  110,  113,
+      113,  113,  113,  113,  115,  254,  115,  115,  115,  120,
 
+      120,  253,  252,  251,  250,  249,  248,  247,  246,  245,
+      244,  243,  242,  241,  240,  239,  238,  237,  236,  235,
       234,  233,  232,  231,  230,  229,  228,  227,  226,  225,
       224,  223,  222,  221,  220,  219,  218,  217,  216,  215,
       214,  213,  212,  211,  210,  209,  208,  207,  206,  205,
       204,  203,  202,  201,  200,  199,  198,  197,  196,  195,
-      194,  193,  192,  191,  190,  189,  188,  187,  184,  183,
+      194,  193,  192,  191,  190,  187,  186,  185,  184,  183,
       182,  181,  180,  179,  178,  177,  176,  175,  174,  173,
-      172,  171,  170,  169,  168,  167,  166,  165,  164,  163,
-      160,  159,  158,  154,  113,  151,  150,  149,  148,  147,
-      146,  145,  144,  140,  139,  138,  137,  136,  135,  134,
-      133,  132,  131,  130,  129,  128,  127,  124,  123,  122,
+      172,  171,  170,  169,  168,  167,  166,  165,  162,  161,
+      160,  156,  114,  153,  152,  151,  150,  149,  148,  147,
 
-      121,  120,  113,  110,  104,  103,  102,   99,   98,   97,
-       96,   95,   90,   86,   80,   75,   60,   57,   54,  252,
-        3,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  252,  252
+      146,  145,  141,  140,  139,  138,  137,  136,  135,  134,
+      133,  132,  131,  130,  129,  128,  125,  124,  123,  122,
+      121,  114,  111,  105,  104,  103,   99,   98,   97,   96,
+       95,   90,   86,   80,   75,   60,   57,   54,  270,    3,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  270,  270
+
     } ;
 
-static const flex_int16_t yy_chk[378] =
+static const flex_int16_t yy_chk[398] =
     {   0,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
-        1,    1,    1,    1,    1,    1,    5,    5,    6,    6,
-       11,   15,   17,   18,   18,  250,   20,   19,   20,   20,
-       17,   15,   19,   21,   11,   21,   21,   20,   19,   24,
-       24,   26,   26,   30,   21,   20,   32,   33,   35,   48,
-       30,   33,   21,   37,   38,   50,   33,   48,   35,   32,
+        1,    1,    1,    1,    1,    1,    1,    5,    5,    6,
+        6,   11,   15,   17,   18,   18,  268,   20,   19,   20,
+       20,   17,   15,   19,   21,   11,   21,   21,   20,   19,
+       24,   24,   26,   26,   30,   21,   32,   20,   33,   37,
+       35,   30,   33,   38,   21,   50,   37,   33,  267,   32,
 
-       37,   35,   20,   44,   53,   53,   44,   38,   65,   65,
-      249,   69,   69,   70,   82,   70,   70,   65,  248,   82,
-       69,   99,  117,  117,   70,   65,  245,   50,   69,   71,
-      244,   71,   70,  240,   71,   71,  239,   99,   99,  111,
-      236,  111,  115,  115,  111,  111,  235,  116,  233,  116,
-      232,  115,  116,  116,  118,  118,  126,  126,  231,  115,
-      152,  152,  153,  153,  155,  230,  155,  156,  156,  155,
-      155,  157,  157,  185,  185,  186,  186,  253,  226,  253,
-      254,  254,  254,  255,  255,  255,  256,  256,  256,  256,
-      256,  257,  225,  257,  257,  257,  258,  258,  224,  223,
+       35,   44,   48,   35,   20,  266,   38,   53,   53,   44,
+       48,  265,   44,   65,   65,   69,   69,  264,   70,  263,
+       70,   70,   65,  261,   69,   99,  118,  118,   50,   70,
+       71,   65,   71,   69,   82,   71,   71,  260,   70,   82,
+      259,   99,   99,  112,  258,  112,  116,  116,  112,  112,
+      257,  117,  256,  117,  254,  116,  117,  117,  119,  119,
+      127,  127,  154,  154,  116,  155,  155,  157,  252,  157,
+      158,  158,  157,  157,  159,  159,  188,  188,  189,  189,
+      271,  251,  271,  272,  272,  272,  273,  273,  273,  274,
+      274,  274,  274,  274,  275,  249,  275,  275,  275,  276,
 
-      222,  218,  217,  216,  215,  214,  212,  211,  210,  209,
-      208,  206,  205,  204,  203,  202,  201,  200,  199,  198,
-      197,  196,  192,  191,  190,  188,  184,  183,  182,  181,
-      179,  178,  177,  176,  175,  174,  173,  172,  170,  169,
-      168,  166,  164,  163,  162,  161,  160,  158,  150,  149,
-      148,  147,  146,  145,  144,  143,  142,  141,  140,  138,
-      137,  136,  135,  134,  133,  131,  130,  129,  128,  127,
-      125,  124,  123,  113,  112,  108,  106,  105,  104,  103,
-      102,  101,  100,   98,   97,   96,   95,   94,   93,   92,
-       90,   89,   88,   87,   86,   84,   83,   81,   80,   79,
+      276,  246,  245,  242,  241,  239,  238,  237,  236,  235,
+      231,  230,  229,  228,  227,  224,  222,  221,  220,  219,
+      218,  216,  215,  214,  213,  212,  210,  209,  208,  207,
+      206,  205,  204,  203,  202,  201,  200,  199,  195,  194,
+      193,  191,  187,  186,  185,  184,  182,  181,  180,  179,
+      178,  177,  176,  175,  174,  172,  171,  170,  168,  166,
+      165,  164,  163,  162,  160,  152,  151,  150,  149,  148,
+      147,  146,  145,  144,  143,  142,  141,  139,  138,  137,
+      136,  135,  134,  132,  131,  130,  129,  128,  126,  125,
+      124,  114,  113,  109,  107,  106,  105,  104,  103,  102,
 
-       77,   73,   66,   58,   47,   46,   45,   43,   42,   41,
-       40,   39,   36,   34,   31,   25,   14,   10,    7,    3,
-      252,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  252,  252,  252,  252,  252,
-      252,  252,  252,  252,  252,  252,  252
+      101,  100,   98,   97,   96,   95,   94,   93,   92,   90,
+       89,   88,   87,   86,   84,   83,   81,   80,   79,   77,
+       73,   66,   58,   47,   46,   45,   43,   42,   41,   40,
+       39,   36,   34,   31,   25,   14,   10,    7,    3,  270,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  270,  270,  270,  270,  270,
+      270,  270,  270,  270,  270,  270,  270
+
     } ;
 
 /* Table of booleans, true if rule could match eol. */
-static const flex_int32_t yy_rule_can_match_eol[93] =
+static const flex_int32_t yy_rule_can_match_eol[94] =
     {   0,
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,     };
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,     };
 
 /* The intent behind this definition is that it'll catch
  * any uses of REJECT which flex missed.
@@ -697,7 +708,7 @@
 
 */
 #define YY_NO_UNISTD_H 1
-#line 694 "lex.sksl.c"
+#line 705 "lex.sksl.c"
 
 #define INITIAL 0
 
@@ -960,7 +971,7 @@
 #line 30 "sksl.flex"
 
 
-#line 957 "lex.sksl.c"
+#line 968 "lex.sksl.c"
 
 	while ( /*CONSTCOND*/1 )		/* loops until end-of-file is reached */
 		{
@@ -987,13 +998,13 @@
 			while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 				{
 				yy_current_state = (int) yy_def[yy_current_state];
-				if ( yy_current_state >= 253 )
+				if ( yy_current_state >= 271 )
 					yy_c = yy_meta[yy_c];
 				}
 			yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
 			++yy_cp;
 			}
-		while ( yy_current_state != 252 );
+		while ( yy_current_state != 270 );
 		yy_cp = yyg->yy_last_accepting_cpos;
 		yy_current_state = yyg->yy_last_accepting_state;
 
@@ -1198,296 +1209,301 @@
 case 35:
 YY_RULE_SETUP
 #line 100 "sksl.flex"
-{ return SkSL::Token::STRUCT; }
+{ return SkSL::Token::HASSIDEEFFECTS; }
 	YY_BREAK
 case 36:
 YY_RULE_SETUP
 #line 102 "sksl.flex"
-{ return SkSL::Token::LAYOUT; }
+{ return SkSL::Token::STRUCT; }
 	YY_BREAK
 case 37:
 YY_RULE_SETUP
 #line 104 "sksl.flex"
-{ return SkSL::Token::PRECISION; }
+{ return SkSL::Token::LAYOUT; }
 	YY_BREAK
 case 38:
 YY_RULE_SETUP
 #line 106 "sksl.flex"
-{ return SkSL::Token::IDENTIFIER; }
+{ return SkSL::Token::PRECISION; }
 	YY_BREAK
 case 39:
 YY_RULE_SETUP
 #line 108 "sksl.flex"
-{ return SkSL::Token::DIRECTIVE; }
+{ return SkSL::Token::IDENTIFIER; }
 	YY_BREAK
 case 40:
 YY_RULE_SETUP
 #line 110 "sksl.flex"
-{ return SkSL::Token::LPAREN; }
+{ return SkSL::Token::DIRECTIVE; }
 	YY_BREAK
 case 41:
 YY_RULE_SETUP
 #line 112 "sksl.flex"
-{ return SkSL::Token::RPAREN; }
+{ return SkSL::Token::LPAREN; }
 	YY_BREAK
 case 42:
 YY_RULE_SETUP
 #line 114 "sksl.flex"
-{ return SkSL::Token::LBRACE; }
+{ return SkSL::Token::RPAREN; }
 	YY_BREAK
 case 43:
 YY_RULE_SETUP
 #line 116 "sksl.flex"
-{ return SkSL::Token::RBRACE; }
+{ return SkSL::Token::LBRACE; }
 	YY_BREAK
 case 44:
 YY_RULE_SETUP
 #line 118 "sksl.flex"
-{ return SkSL::Token::LBRACKET; }
+{ return SkSL::Token::RBRACE; }
 	YY_BREAK
 case 45:
 YY_RULE_SETUP
 #line 120 "sksl.flex"
-{ return SkSL::Token::RBRACKET; }
+{ return SkSL::Token::LBRACKET; }
 	YY_BREAK
 case 46:
 YY_RULE_SETUP
 #line 122 "sksl.flex"
-{ return SkSL::Token::DOT; }
+{ return SkSL::Token::RBRACKET; }
 	YY_BREAK
 case 47:
 YY_RULE_SETUP
 #line 124 "sksl.flex"
-{ return SkSL::Token::COMMA; }
+{ return SkSL::Token::DOT; }
 	YY_BREAK
 case 48:
 YY_RULE_SETUP
 #line 126 "sksl.flex"
-{ return SkSL::Token::PLUSPLUS; }
+{ return SkSL::Token::COMMA; }
 	YY_BREAK
 case 49:
 YY_RULE_SETUP
 #line 128 "sksl.flex"
-{ return SkSL::Token::MINUSMINUS; }
+{ return SkSL::Token::PLUSPLUS; }
 	YY_BREAK
 case 50:
 YY_RULE_SETUP
 #line 130 "sksl.flex"
-{ return SkSL::Token::PLUS; }
+{ return SkSL::Token::MINUSMINUS; }
 	YY_BREAK
 case 51:
 YY_RULE_SETUP
 #line 132 "sksl.flex"
-{ return SkSL::Token::MINUS; }
+{ return SkSL::Token::PLUS; }
 	YY_BREAK
 case 52:
 YY_RULE_SETUP
 #line 134 "sksl.flex"
-{ return SkSL::Token::STAR; }
+{ return SkSL::Token::MINUS; }
 	YY_BREAK
 case 53:
 YY_RULE_SETUP
 #line 136 "sksl.flex"
-{ return SkSL::Token::SLASH; }
+{ return SkSL::Token::STAR; }
 	YY_BREAK
 case 54:
 YY_RULE_SETUP
 #line 138 "sksl.flex"
-{ return SkSL::Token::PERCENT; }
+{ return SkSL::Token::SLASH; }
 	YY_BREAK
 case 55:
 YY_RULE_SETUP
 #line 140 "sksl.flex"
-{ return SkSL::Token::SHL; }
+{ return SkSL::Token::PERCENT; }
 	YY_BREAK
 case 56:
 YY_RULE_SETUP
 #line 142 "sksl.flex"
-{ return SkSL::Token::SHR; }
+{ return SkSL::Token::SHL; }
 	YY_BREAK
 case 57:
 YY_RULE_SETUP
 #line 144 "sksl.flex"
-{ return SkSL::Token::BITWISEOR; }
+{ return SkSL::Token::SHR; }
 	YY_BREAK
 case 58:
 YY_RULE_SETUP
 #line 146 "sksl.flex"
-{ return SkSL::Token::BITWISEXOR; }
+{ return SkSL::Token::BITWISEOR; }
 	YY_BREAK
 case 59:
 YY_RULE_SETUP
 #line 148 "sksl.flex"
-{ return SkSL::Token::BITWISEAND; }
+{ return SkSL::Token::BITWISEXOR; }
 	YY_BREAK
 case 60:
 YY_RULE_SETUP
 #line 150 "sksl.flex"
-{ return SkSL::Token::BITWISENOT; }
+{ return SkSL::Token::BITWISEAND; }
 	YY_BREAK
 case 61:
 YY_RULE_SETUP
 #line 152 "sksl.flex"
-{ return SkSL::Token::LOGICALOR; }
+{ return SkSL::Token::BITWISENOT; }
 	YY_BREAK
 case 62:
 YY_RULE_SETUP
 #line 154 "sksl.flex"
-{ return SkSL::Token::LOGICALXOR; }
+{ return SkSL::Token::LOGICALOR; }
 	YY_BREAK
 case 63:
 YY_RULE_SETUP
 #line 156 "sksl.flex"
-{ return SkSL::Token::LOGICALAND; }
+{ return SkSL::Token::LOGICALXOR; }
 	YY_BREAK
 case 64:
 YY_RULE_SETUP
 #line 158 "sksl.flex"
-{ return SkSL::Token::LOGICALNOT; }
+{ return SkSL::Token::LOGICALAND; }
 	YY_BREAK
 case 65:
 YY_RULE_SETUP
 #line 160 "sksl.flex"
-{ return SkSL::Token::QUESTION; }
+{ return SkSL::Token::LOGICALNOT; }
 	YY_BREAK
 case 66:
 YY_RULE_SETUP
 #line 162 "sksl.flex"
-{ return SkSL::Token::COLON; }
+{ return SkSL::Token::QUESTION; }
 	YY_BREAK
 case 67:
 YY_RULE_SETUP
 #line 164 "sksl.flex"
-{ return SkSL::Token::EQ; }
+{ return SkSL::Token::COLON; }
 	YY_BREAK
 case 68:
 YY_RULE_SETUP
 #line 166 "sksl.flex"
-{ return SkSL::Token::EQEQ; }
+{ return SkSL::Token::EQ; }
 	YY_BREAK
 case 69:
 YY_RULE_SETUP
 #line 168 "sksl.flex"
-{ return SkSL::Token::NEQ; }
+{ return SkSL::Token::EQEQ; }
 	YY_BREAK
 case 70:
 YY_RULE_SETUP
 #line 170 "sksl.flex"
-{ return SkSL::Token::GT; }
+{ return SkSL::Token::NEQ; }
 	YY_BREAK
 case 71:
 YY_RULE_SETUP
 #line 172 "sksl.flex"
-{ return SkSL::Token::LT; }
+{ return SkSL::Token::GT; }
 	YY_BREAK
 case 72:
 YY_RULE_SETUP
 #line 174 "sksl.flex"
-{ return SkSL::Token::GTEQ; }
+{ return SkSL::Token::LT; }
 	YY_BREAK
 case 73:
 YY_RULE_SETUP
 #line 176 "sksl.flex"
-{ return SkSL::Token::LTEQ; }
+{ return SkSL::Token::GTEQ; }
 	YY_BREAK
 case 74:
 YY_RULE_SETUP
 #line 178 "sksl.flex"
-{ return SkSL::Token::PLUSEQ; }
+{ return SkSL::Token::LTEQ; }
 	YY_BREAK
 case 75:
 YY_RULE_SETUP
 #line 180 "sksl.flex"
-{ return SkSL::Token::MINUSEQ; }
+{ return SkSL::Token::PLUSEQ; }
 	YY_BREAK
 case 76:
 YY_RULE_SETUP
 #line 182 "sksl.flex"
-{ return SkSL::Token::STAREQ; }
+{ return SkSL::Token::MINUSEQ; }
 	YY_BREAK
 case 77:
 YY_RULE_SETUP
 #line 184 "sksl.flex"
-{ return SkSL::Token::SLASHEQ; }
+{ return SkSL::Token::STAREQ; }
 	YY_BREAK
 case 78:
 YY_RULE_SETUP
 #line 186 "sksl.flex"
-{ return SkSL::Token::PERCENTEQ; }
+{ return SkSL::Token::SLASHEQ; }
 	YY_BREAK
 case 79:
 YY_RULE_SETUP
 #line 188 "sksl.flex"
-{ return SkSL::Token::SHLEQ; }
+{ return SkSL::Token::PERCENTEQ; }
 	YY_BREAK
 case 80:
 YY_RULE_SETUP
 #line 190 "sksl.flex"
-{ return SkSL::Token::SHREQ; }
+{ return SkSL::Token::SHLEQ; }
 	YY_BREAK
 case 81:
 YY_RULE_SETUP
 #line 192 "sksl.flex"
-{ return SkSL::Token::BITWISEOREQ; }
+{ return SkSL::Token::SHREQ; }
 	YY_BREAK
 case 82:
 YY_RULE_SETUP
 #line 194 "sksl.flex"
-{ return SkSL::Token::BITWISEXOREQ; }
+{ return SkSL::Token::BITWISEOREQ; }
 	YY_BREAK
 case 83:
 YY_RULE_SETUP
 #line 196 "sksl.flex"
-{ return SkSL::Token::BITWISEANDEQ; }
+{ return SkSL::Token::BITWISEXOREQ; }
 	YY_BREAK
 case 84:
 YY_RULE_SETUP
 #line 198 "sksl.flex"
-{ return SkSL::Token::LOGICALOREQ; }
+{ return SkSL::Token::BITWISEANDEQ; }
 	YY_BREAK
 case 85:
 YY_RULE_SETUP
 #line 200 "sksl.flex"
-{ return SkSL::Token::LOGICALXOREQ; }
+{ return SkSL::Token::LOGICALOREQ; }
 	YY_BREAK
 case 86:
 YY_RULE_SETUP
 #line 202 "sksl.flex"
-{ return SkSL::Token::LOGICALANDEQ; }
+{ return SkSL::Token::LOGICALXOREQ; }
 	YY_BREAK
 case 87:
 YY_RULE_SETUP
 #line 204 "sksl.flex"
-{ return SkSL::Token::SEMICOLON; }
+{ return SkSL::Token::LOGICALANDEQ; }
 	YY_BREAK
 case 88:
 YY_RULE_SETUP
 #line 206 "sksl.flex"
-/* line comment */
+{ return SkSL::Token::SEMICOLON; }
 	YY_BREAK
 case 89:
-/* rule 89 can match eol */
 YY_RULE_SETUP
 #line 208 "sksl.flex"
-/* block comment */
+/* line comment */
 	YY_BREAK
 case 90:
 /* rule 90 can match eol */
 YY_RULE_SETUP
 #line 210 "sksl.flex"
-/* whitespace */
+/* block comment */
 	YY_BREAK
 case 91:
+/* rule 91 can match eol */
 YY_RULE_SETUP
 #line 212 "sksl.flex"
-{ return SkSL::Token::INVALID_TOKEN; }
+/* whitespace */
 	YY_BREAK
 case 92:
 YY_RULE_SETUP
 #line 214 "sksl.flex"
+{ return SkSL::Token::INVALID_TOKEN; }
+	YY_BREAK
+case 93:
+YY_RULE_SETUP
+#line 216 "sksl.flex"
 ECHO;
 	YY_BREAK
-#line 1484 "lex.sksl.c"
+#line 1500 "lex.sksl.c"
 case YY_STATE_EOF(INITIAL):
 	yyterminate();
 
@@ -1783,7 +1799,7 @@
 		while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 			{
 			yy_current_state = (int) yy_def[yy_current_state];
-			if ( yy_current_state >= 253 )
+			if ( yy_current_state >= 271 )
 				yy_c = yy_meta[yy_c];
 			}
 		yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
@@ -1812,11 +1828,11 @@
 	while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 		{
 		yy_current_state = (int) yy_def[yy_current_state];
-		if ( yy_current_state >= 253 )
+		if ( yy_current_state >= 271 )
 			yy_c = yy_meta[yy_c];
 		}
 	yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
-	yy_is_jam = (yy_current_state == 252);
+	yy_is_jam = (yy_current_state == 270);
 
 	(void)yyg;
 	return yy_is_jam ? 0 : yy_current_state;
@@ -2664,7 +2680,7 @@
 
 #define YYTABLES_NAME "yytables"
 
-#line 214 "sksl.flex"
+#line 216 "sksl.flex"
 
 
 int skslwrap(yyscan_t scanner) {
diff --git a/src/sksl/sksl.flex b/src/sksl/sksl.flex
index bbb106c..2dd4225 100644
--- a/src/sksl/sksl.flex
+++ b/src/sksl/sksl.flex
@@ -97,6 +97,8 @@
 
 restrict { return SkSL::Token::RESTRICT; }
 
+sk_has_side_effects { return SkSL::Token::HASSIDEEFFECTS; }
+
 struct { return SkSL::Token::STRUCT; }
 
 layout { return SkSL::Token::LAYOUT; }
diff --git a/src/sksl/sksl_frag.include b/src/sksl/sksl_frag.include
index d65545d..f1192fe 100644
--- a/src/sksl/sksl_frag.include
+++ b/src/sksl/sksl_frag.include
@@ -14,7 +14,7 @@
 layout(builtin=9999) vec4 gl_LastFragColorARM;
 layout(builtin=9999) int gl_SampleMaskIn[1];
 layout(builtin=9999) out int gl_SampleMask[1];
-layout(builtin=9999) vec4 gl_SecondaryFragColorEXT;
+layout(builtin=9999) out vec4 gl_SecondaryFragColorEXT;
 
 layout(location=0,index=0,builtin=10001) out vec4 sk_FragColor;
 
diff --git a/src/sksl/sksl_geom.include b/src/sksl/sksl_geom.include
index 18e779f..e980d6b 100644
--- a/src/sksl/sksl_geom.include
+++ b/src/sksl/sksl_geom.include
@@ -16,9 +16,9 @@
 
 layout(builtin=8) int sk_InvocationID;
 
-void EmitStreamVertex(int stream);
-void EndStreamPrimitive(int stream);
-void EmitVertex();
-void EndPrimitive();
+sk_has_side_effects void EmitStreamVertex(int stream);
+sk_has_side_effects void EndStreamPrimitive(int stream);
+sk_has_side_effects void EmitVertex();
+sk_has_side_effects void EndPrimitive();
 
 )
diff --git a/tests/SkSLGLSLTest.cpp b/tests/SkSLGLSLTest.cpp
index 6151b94..97d7acb 100644
--- a/tests/SkSLGLSLTest.cpp
+++ b/tests/SkSLGLSLTest.cpp
@@ -11,6 +11,13 @@
 
 #if SK_SUPPORT_GPU
 
+// Note that the optimizer will aggressively kill dead code and substitute constants in place of
+// variables, so we have to jump through a few hoops to ensure that the code in these tests has the
+// necessary side-effects to remain live. In some cases we rely on the optimizer not (yet) being
+// smart enough to optimize around certain constructs; as the optimizer gets smarter it will
+// undoubtedly end up breaking some of these tests. That is a good thing, as long as the new code is
+// equivalent!
+
 static void test(skiatest::Reporter* r, const char* src, const SkSL::Program::Settings& settings,
                  const char* expected, SkSL::Program::Inputs* inputs,
                  SkSL::Program::Kind kind = SkSL::Program::kFragment_Kind) {
@@ -57,7 +64,7 @@
          "void main() {"
          "if (sqrt(2) > 5) { sk_FragColor = vec4(0.75); } else { discard; }"
          "int i = 0;"
-         "while (i < 10) sk_FragColor *= 0.5;"
+         "while (i < 10) { sk_FragColor *= 0.5; i++; }"
          "do { sk_FragColor += 0.01; } while (sk_FragColor.x < 0.75);"
          "for (int i = 0; i < 10; i++) {"
          "if (i % 2 == 1) break; else continue;"
@@ -74,7 +81,10 @@
          "        discard;\n"
          "    }\n"
          "    int i = 0;\n"
-         "    while (true) sk_FragColor *= 0.5;\n"
+         "    while (i < 10) {\n"
+         "        sk_FragColor *= 0.5;\n"
+         "        i++;\n"
+         "    }\n"
          "    do {\n"
          "        sk_FragColor += 0.01;\n"
          "    } while (sk_FragColor.x < 0.75);\n"
@@ -105,8 +115,8 @@
          "}\n"
          "void main() {\n"
          "    float x = 10.0;\n"
-         "    bar(10.0);\n"
-         "    sk_FragColor = vec4(10.0);\n"
+         "    bar(x);\n"
+         "    sk_FragColor = vec4(x);\n"
          "}\n");
 }
 
@@ -165,16 +175,16 @@
          "mat3x4 z = x * y;"
          "vec3 v1 = mat3(1) * vec3(1);"
          "vec3 v2 = vec3(1) * mat3(1);"
+         "sk_FragColor = vec4(z[0].x, v1 + v2);"
          "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    mat2x4 x = mat2x4(1.0);\n"
-         "    mat3x2 y = mat3x2(1.0, 0.0, 0.0, 1.0, vec2(2.0, 2.0));\n"
-         "    mat3x4 z = x * y;\n"
+         "    mat3x4 z = mat2x4(1.0) * mat3x2(1.0, 0.0, 0.0, 1.0, vec2(2.0, 2.0));\n"
          "    vec3 v1 = mat3(1.0) * vec3(1.0);\n"
          "    vec3 v2 = vec3(1.0) * mat3(1.0);\n"
+         "    sk_FragColor = vec4(z[0].x, v1 + v2);\n"
          "}\n");
 }
 
@@ -289,16 +299,21 @@
 
 DEF_TEST(SkSLUsesPrecisionModifiers, r) {
     test(r,
-         "void main() { float x = 0.75; highp float y = 1; }",
+         "void main() { float x = 0.75; highp float y = 1; x++; y++;"
+         "sk_FragColor.rg = vec2(x, y); }",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
          "    float x = 0.75;\n"
          "    float y = 1.0;\n"
-         "}\n");    
+         "    x++;\n"
+         "    y++;\n"
+         "    sk_FragColor.xy = vec2(x, y);\n"
+         "}\n");
     test(r,
-         "void main() { float x = 0.75; highp float y = 1; }",
+         "void main() { float x = 0.75; highp float y = 1; x++; y++;"
+         "sk_FragColor.rg = vec2(x, y); }",
          *SkSL::ShaderCapsFactory::UsesPrecisionModifiers(),
          "#version 400\n"
          "precision highp float;\n"
@@ -306,27 +321,29 @@
          "void main() {\n"
          "    float x = 0.75;\n"
          "    highp float y = 1.0;\n"
-         "}\n");    
+         "    x++;\n"
+         "    y++;\n"
+         "    sk_FragColor.xy = vec2(x, y);\n"
+         "}\n");
 }
 
 DEF_TEST(SkSLMinAbs, r) {
     test(r,
          "void main() {"
          "float x = -5;"
-         "x = min(abs(x), 6);"
+         "sk_FragColor.r = min(abs(x), 6);"
          "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    float x = -5.0;\n"
-         "    x = min(abs(-5.0), 6.0);\n"
+         "    sk_FragColor.x = min(abs(-5.0), 6.0);\n"
          "}\n");
 
     test(r,
          "void main() {"
          "float x = -5.0;"
-         "x = min(abs(x), 6.0);"
+         "sk_FragColor.r = min(abs(x), 6.0);"
          "}",
          *SkSL::ShaderCapsFactory::CannotUseMinAndAbsTogether(),
          "#version 400\n"
@@ -334,30 +351,29 @@
          "void main() {\n"
          "    float minAbsHackVar0;\n"
          "    float minAbsHackVar1;\n"
-         "    float x = -5.0;\n"
-         "    x = ((minAbsHackVar0 = abs(-5.0)) < (minAbsHackVar1 = 6.0) ? minAbsHackVar0 : "
-                                                                                "minAbsHackVar1);\n"
+         "    sk_FragColor.x = ((minAbsHackVar0 = abs(-5.0)) < (minAbsHackVar1 = 6.0) ? "
+                                                               "minAbsHackVar0 : minAbsHackVar1);\n"
          "}\n");
 }
 
 DEF_TEST(SkSLNegatedAtan, r) {
     test(r,
-         "void main() { vec2 x = vec2(1, 2); float y = atan(x.x, -(2 * x.y)); }",
+         "void main() { vec2 x = vec2(sqrt(2)); sk_FragColor.r = atan(x.x, -x.y); }",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    vec2 x = vec2(1.0, 2.0);\n"
-         "    float y = atan(x.x, -(2.0 * x.y));\n"
+         "    vec2 x = vec2(sqrt(2.0));\n"
+         "    sk_FragColor.x = atan(x.x, -x.y);\n"
          "}\n");
     test(r,
-         "void main() { vec2 x = vec2(1, 2); float y = atan(x.x, -(2 * x.y)); }",
+         "void main() { vec2 x = vec2(sqrt(2)); sk_FragColor.r = atan(x.x, -x.y); }",
          *SkSL::ShaderCapsFactory::MustForceNegatedAtanParamToFloat(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    vec2 x = vec2(1.0, 2.0);\n"
-         "    float y = atan(x.x, -1.0 * (2.0 * x.y));\n"
+         "    vec2 x = vec2(sqrt(2.0));\n"
+         "    sk_FragColor.x = atan(x.x, -1.0 * x.y);\n"
          "}\n");
 }
 
@@ -377,28 +393,46 @@
     test(r,
          "void main() {"
          "int i1 = 0x0;"
+         "i1++;"
          "int i2 = 0x1234abcd;"
+         "i2++;"
          "int i3 = 0x7fffffff;"
+         "i3++;"
          "int i4 = 0xffffffff;"
+         "i4++;"
          "int i5 = -0xbeef;"
+         "i5++;"
          "uint u1 = 0x0;"
+         "u1++;"
          "uint u2 = 0x1234abcd;"
+         "u2++;"
          "uint u3 = 0x7fffffff;"
+         "u3++;"
          "uint u4 = 0xffffffff;"
+         "u4++;"
          "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
          "    int i1 = 0;\n"
+         "    i1++;\n"
          "    int i2 = 305441741;\n"
+         "    i2++;\n"
          "    int i3 = 2147483647;\n"
+         "    i3++;\n"
          "    int i4 = -1;\n"
+         "    i4++;\n"
          "    int i5 = -48879;\n"
+         "    i5++;\n"
          "    uint u1 = 0u;\n"
+         "    u1++;\n"
          "    uint u2 = 305441741u;\n"
+         "    u2++;\n"
          "    uint u3 = 2147483647u;\n"
+         "    u3++;\n"
          "    uint u4 = 4294967295u;\n"
+         "    u4++;\n"
          "}\n");
 }
 
@@ -438,29 +472,29 @@
 
 DEF_TEST(SkSLDerivatives, r) {
     test(r,
-         "void main() { float x = dFdx(1); }",
+         "void main() { sk_FragColor.r = dFdx(1); }",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    float x = dFdx(1.0);\n"
+         "    sk_FragColor.x = dFdx(1.0);\n"
          "}\n");
     test(r,
-         "void main() { float x = 1; }",
+         "void main() { sk_FragColor.r = 1; }",
          *SkSL::ShaderCapsFactory::ShaderDerivativeExtensionString(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    float x = 1.0;\n"
+         "    sk_FragColor.x = 1.0;\n"
          "}\n");
     test(r,
-         "void main() { float x = dFdx(1); }",
+         "void main() { sk_FragColor.r = dFdx(1); }",
          *SkSL::ShaderCapsFactory::ShaderDerivativeExtensionString(),
          "#version 400\n"
          "#extension GL_OES_standard_derivatives : require\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    float x = dFdx(1.0);\n"
+         "    sk_FragColor.x = dFdx(1.0);\n"
          "}\n");
 }
 
@@ -468,76 +502,121 @@
     test(r,
          "void main() {"
          "float f_add = 32 + 2;"
+         "sk_FragColor.r = f_add;"
          "float f_sub = 32 - 2;"
+         "sk_FragColor.r = f_sub;"
          "float f_mul = 32 * 2;"
+         "sk_FragColor.r = f_mul;"
          "float f_div = 32 / 2;"
+         "sk_FragColor.r = f_div;"
          "float mixed = (12 > 2.0) ? (10 * 2 / 5 + 18 - 3) : 0;"
+         "sk_FragColor.r = mixed;"
          "int i_add = 32 + 2;"
+         "sk_FragColor.r = i_add;"
          "int i_sub = 32 - 2;"
+         "sk_FragColor.r = i_sub;"
          "int i_mul = 32 * 2;"
+         "sk_FragColor.r = i_mul;"
          "int i_div = 32 / 2;"
+         "sk_FragColor.r = i_div;"
          "int i_or = 12 | 6;"
+         "sk_FragColor.r = i_or;"
          "int i_and = 254 & 7;"
+         "sk_FragColor.r = i_and;"
          "int i_xor = 2 ^ 7;"
+         "sk_FragColor.r = i_xor;"
          "int i_shl = 1 << 4;"
+         "sk_FragColor.r = i_shl;"
          "int i_shr = 128 >> 2;"
+         "sk_FragColor.r = i_shr;"
          "bool gt_it = 6 > 5;"
+         "sk_FragColor.r = true ? 1 : 0;"
          "bool gt_if = 6 > 6;"
+         "sk_FragColor.r = gt_if ? 1 : 0;"
          "bool gt_ft = 6.0 > 5.0;"
+         "sk_FragColor.r = gt_ft ? 1 : 0;"
          "bool gt_ff = 6.0 > 6.0;"
+         "sk_FragColor.r = gt_ff ? 1 : 0;"
          "bool gte_it = 6 >= 6;"
+         "sk_FragColor.r = gte_it ? 1 : 0;"
          "bool gte_if = 6 >= 7;"
+         "sk_FragColor.r = gte_if ? 1 : 0;"
          "bool gte_ft = 6.0 >= 6.0;"
+         "sk_FragColor.r = gte_ft ? 1 : 0;"
          "bool gte_ff = 6.0 >= 7.0;"
+         "sk_FragColor.r = gte_ff ? 1 : 0;"
          "bool lte_it = 6 <= 6;"
+         "sk_FragColor.r = lte_it ? 1 : 0;"
          "bool lte_if = 6 <= 5;"
+         "sk_FragColor.r = lte_if ? 1 : 0;"
          "bool lte_ft = 6.0 <= 6.0;"
+         "sk_FragColor.r = lte_ft ? 1 : 0;"
          "bool lte_ff = 6.0 <= 5.0;"
+         "sk_FragColor.r = lte_ff ? 1 : 0;"
          "bool or_t = 1 == 1 || 2 == 8;"
+         "sk_FragColor.r = or_t ? 1 : 0;"
          "bool or_f = 1 > 1 || 2 == 8;"
+         "sk_FragColor.r = or_f ? 1 : 0;"
          "bool and_t = 1 == 1 && 2 <= 8;"
+         "sk_FragColor.r = and_t ? 1 : 0;"
          "bool and_f = 1 == 2 && 2 == 8;"
+         "sk_FragColor.r = and_f ? 1 : 0;"
          "bool xor_t = 1 == 1 ^^ 1 != 1;"
+         "sk_FragColor.r = xor_t ? 1 : 0;"
          "bool xor_f = 1 == 1 ^^ 1 == 1;"
+         "sk_FragColor.r = xor_f ? 1 : 0;"
          "int ternary = 10 > 5 ? 10 : 5;"
+         "sk_FragColor.r = ternary;"
+         "sk_FragColor.r = vec4(0.5, 1, 1, 1).x;"
+         "sk_FragColor = vec4(vec2(1), vec2(2, 3)) + vec4(5, 6, 7, 8);"
+         "sk_FragColor = vec4(8, vec3(10)) - vec4(1);"
+         "sk_FragColor = vec4(2) * vec4(1, 2, 3, 4);"
+         "sk_FragColor = vec4(12) / vec4(1, 2, 3, 4);"
+         "sk_FragColor.r = (vec4(12) / vec4(1, 2, 3, 4)).y;"
          "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    float f_add = 34.0;\n"
-         "    float f_sub = 30.0;\n"
-         "    float f_mul = 64.0;\n"
-         "    float f_div = 16.0;\n"
-         "    float mixed = 19.0;\n"
-         "    int i_add = 34;\n"
-         "    int i_sub = 30;\n"
-         "    int i_mul = 64;\n"
-         "    int i_div = 16;\n"
-         "    int i_or = 14;\n"
-         "    int i_and = 6;\n"
-         "    int i_xor = 5;\n"
-         "    int i_shl = 16;\n"
-         "    int i_shr = 32;\n"
-         "    bool gt_it = true;\n"
-         "    bool gt_if = false;\n"
-         "    bool gt_ft = true;\n"
-         "    bool gt_ff = false;\n"
-         "    bool gte_it = true;\n"
-         "    bool gte_if = false;\n"
-         "    bool gte_ft = true;\n"
-         "    bool gte_ff = false;\n"
-         "    bool lte_it = true;\n"
-         "    bool lte_if = false;\n"
-         "    bool lte_ft = true;\n"
-         "    bool lte_ff = false;\n"
-         "    bool or_t = true;\n"
-         "    bool or_f = false;\n"
-         "    bool and_t = true;\n"
-         "    bool and_f = false;\n"
-         "    bool xor_t = true;\n"
-         "    bool xor_f = false;\n"
-         "    int ternary = 10;\n"
+         "    sk_FragColor.x = 34.0;\n"
+         "    sk_FragColor.x = 30.0;\n"
+         "    sk_FragColor.x = 64.0;\n"
+         "    sk_FragColor.x = 16.0;\n"
+         "    sk_FragColor.x = 19.0;\n"
+         "    sk_FragColor.x = 34.0;\n"
+         "    sk_FragColor.x = 30.0;\n"
+         "    sk_FragColor.x = 64.0;\n"
+         "    sk_FragColor.x = 16.0;\n"
+         "    sk_FragColor.x = 14.0;\n"
+         "    sk_FragColor.x = 6.0;\n"
+         "    sk_FragColor.x = 5.0;\n"
+         "    sk_FragColor.x = 16.0;\n"
+         "    sk_FragColor.x = 32.0;\n"
+         "    sk_FragColor.x = 1.0;\n"
+         "    sk_FragColor.x = 0.0;\n"
+         "    sk_FragColor.x = 1.0;\n"
+         "    sk_FragColor.x = 0.0;\n"
+         "    sk_FragColor.x = 1.0;\n"
+         "    sk_FragColor.x = 0.0;\n"
+         "    sk_FragColor.x = 1.0;\n"
+         "    sk_FragColor.x = 0.0;\n"
+         "    sk_FragColor.x = 1.0;\n"
+         "    sk_FragColor.x = 0.0;\n"
+         "    sk_FragColor.x = 1.0;\n"
+         "    sk_FragColor.x = 0.0;\n"
+         "    sk_FragColor.x = 1.0;\n"
+         "    sk_FragColor.x = 0.0;\n"
+         "    sk_FragColor.x = 1.0;\n"
+         "    sk_FragColor.x = 0.0;\n"
+         "    sk_FragColor.x = 1.0;\n"
+         "    sk_FragColor.x = 0.0;\n"
+         "    sk_FragColor.x = 10.0;\n"
+         "    sk_FragColor.x = 0.5;\n"
+         "    sk_FragColor = vec4(6.0, 7.0, 9.0, 11.0);\n"
+         "    sk_FragColor = vec4(7.0, 9.0, 9.0, 9.0);\n"
+         "    sk_FragColor = vec4(2.0, 4.0, 6.0, 8.0);\n"
+         "    sk_FragColor = vec4(12.0, 6.0, 4.0, 3.0);\n"
+         "    sk_FragColor.x = 6.0;\n"
          "}\n");
 }
 
@@ -549,40 +628,34 @@
          "if (2 > 1) x = 2; else x = 3;"
          "if (1 > 2) x = 4; else x = 5;"
          "if (false) x = 6;"
+         "sk_FragColor.r = x;"
          "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    int x;\n"
-         "    x = 1;\n"
-         "    x = 2;\n"
-         "    x = 5;\n"
-         "    {\n"
-         "    }\n"
+         "    sk_FragColor.x = 5.0;\n"
          "}\n");
 }
 
 DEF_TEST(SkSLCaps, r) {
     test(r,
          "void main() {"
-         "int x;"
+         "int x = 0;"
+         "int y = 0;"
+         "int z = 0;"
+         "int w = 0;"
          "if (sk_Caps.externalTextureSupport) x = 1;"
-         "if (sk_Caps.fbFetchSupport) x = 2;"
-         "if (sk_Caps.dropsTileOnZeroDivide && sk_Caps.texelFetchSupport) x = 3;"
-         "if (sk_Caps.dropsTileOnZeroDivide && sk_Caps.canUseAnyFunctionInShader) x = 4;"
+         "if (sk_Caps.fbFetchSupport) y = 1;"
+         "if (sk_Caps.dropsTileOnZeroDivide && sk_Caps.texelFetchSupport) z = 1;"
+         "if (sk_Caps.dropsTileOnZeroDivide && sk_Caps.canUseAnyFunctionInShader) w = 1;"
+         "sk_FragColor = vec4(x, y, z, w);"
          "}",
          *SkSL::ShaderCapsFactory::VariousCaps(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    int x;\n"
-         "    x = 1;\n"
-         "    {\n"
-         "    }\n"
-         "    x = 3;\n"
-         "    {\n"
-         "    }\n"
+         "    sk_FragColor = vec4(1.0, 0.0, 1.0, 0.0);\n"
          "}\n");
 }
 
@@ -595,6 +668,7 @@
          "vec4 b = texture(two, vec2(0));"
          "vec4 c = texture(one, vec2(0));"
          "vec4 d = texture(two, vec3(0));"
+         "sk_FragColor = vec4(a.x, b.x, c.x, d.x);"
          "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
@@ -606,6 +680,7 @@
          "    vec4 b = texture(two, vec2(0.0));\n"
          "    vec4 c = textureProj(one, vec2(0.0));\n"
          "    vec4 d = textureProj(two, vec3(0.0));\n"
+         "    sk_FragColor = vec4(a.x, b.x, c.x, d.x);\n"
          "}\n");
     test(r,
          "uniform sampler1D one;"
@@ -615,6 +690,7 @@
          "vec4 b = texture(two, vec2(0));"
          "vec4 c = texture(one, vec2(0));"
          "vec4 d = texture(two, vec3(0));"
+         "sk_FragColor = vec4(a.x, b.x, c.x, d.x);"
          "}",
          *SkSL::ShaderCapsFactory::Version110(),
          "#version 110\n"
@@ -625,6 +701,7 @@
          "    vec4 b = texture2D(two, vec2(0.0));\n"
          "    vec4 c = texture1DProj(one, vec2(0.0));\n"
          "    vec4 d = texture2DProj(two, vec3(0.0));\n"
+         "    gl_FragColor = vec4(a.x, b.x, c.x, d.x);\n"
          "}\n");
 }
 
@@ -740,13 +817,14 @@
 DEF_TEST(SkSLArrayTypes, r) {
     test(r,
          "void main() { vec2 x[2] = vec2[2](vec2(1), vec2(2));"
-         "vec2[2] y = vec2[2](vec2(3), vec2(4)); }",
+         "vec2[2] y = vec2[2](vec2(3), vec2(4));"
+         "sk_FragColor = vec4(x[0], y[1]); }",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    vec2 x[2] = vec2[2](vec2(1.0), vec2(2.0));\n"
-         "    vec2[2] y = vec2[2](vec2(3.0), vec2(4.0));\n"
+         "    sk_FragColor = vec4(vec2[2](vec2(1.0), vec2(2.0))[0], "
+                                 "vec2[2](vec2(3.0), vec2(4.0))[1]);\n"
          "}\n");
 }
 
@@ -827,14 +905,13 @@
          "#version 400\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    float x;\n"
          "    switch (2) {\n"
          "        case 0:\n"
-         "            x = 0.0;\n"
+         "            ;\n"
          "        case 1:\n"
-         "            x = 1.0;\n"
+         "            ;\n"
          "        default:\n"
-         "            x = 2.0;\n"
+         "            ;\n"
          "    }\n"
          "    sk_FragColor = vec4(2.0);\n"
          "}\n");
@@ -903,4 +980,26 @@
          "}\n");
 }
 
+DEF_TEST(SkSLUnusedVars, r) {
+    test(r,
+         "void main() {"
+         "float a = 1, b = 2, c = 3;"
+         "float d = c;"
+         "float e = d;"
+         "b++;"
+         "d++;"
+         "sk_FragColor = vec4(b, b, d, d);"
+         "}",
+        *SkSL::ShaderCapsFactory::Default(),
+         "#version 400\n"
+         "out vec4 sk_FragColor;\n"
+         "void main() {\n"
+         "    float b = 2.0;\n"
+         "    float d = 3.0;\n"
+         "    b++;\n"
+         "    d++;\n"
+         "    sk_FragColor = vec4(b, b, d, d);\n"
+         "}\n");
+}
+
 #endif