moved SkSL Variable data into IRNode

Change-Id: I53af66c1b65971c204ac7c515e0d0e39481b015d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/323097
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index 5277aa2..5e57629 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -160,7 +160,7 @@
             const SkSL::Type& varType = var.type();
 
             // Varyings (only used in conjunction with drawVertices)
-            if (var.fModifiers.fFlags & SkSL::Modifiers::kVarying_Flag) {
+            if (var.modifiers().fFlags & SkSL::Modifiers::kVarying_Flag) {
                 varyings.push_back({var.name(),
                                     varType.typeKind() == SkSL::Type::TypeKind::kVector
                                             ? varType.columns()
@@ -172,7 +172,7 @@
                 sampleUsages.push_back(SkSL::Analysis::GetSampleUsage(*program, var));
             }
             // 'uniform' variables
-            else if (var.fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) {
+            else if (var.modifiers().fFlags & SkSL::Modifiers::kUniform_Flag) {
                 Uniform uni;
                 uni.fName = var.name();
                 uni.fFlags = 0;
@@ -189,7 +189,7 @@
                     RETURN_FAILURE("Invalid uniform type: '%s'", type->displayName().c_str());
                 }
 
-                const SkSL::StringFragment& marker(var.fModifiers.fLayout.fMarker);
+                const SkSL::StringFragment& marker(var.modifiers().fLayout.fMarker);
                 if (marker.fLength) {
                     uni.fFlags |= Uniform::kMarker_Flag;
                     allowColorFilter = false;
@@ -199,7 +199,7 @@
                     }
                 }
 
-                if (var.fModifiers.fLayout.fFlags & SkSL::Layout::Flag::kSRGBUnpremul_Flag) {
+                if (var.modifiers().fLayout.fFlags & SkSL::Layout::Flag::kSRGBUnpremul_Flag) {
                     uni.fFlags |= Uniform::kSRGBUnpremul_Flag;
                 }
 
diff --git a/src/sksl/SkSLAnalysis.cpp b/src/sksl/SkSLAnalysis.cpp
index 9804945..857bdc3 100644
--- a/src/sksl/SkSLAnalysis.cpp
+++ b/src/sksl/SkSLAnalysis.cpp
@@ -139,7 +139,7 @@
     bool visitExpression(const Expression& e) override {
         if (e.is<VariableReference>()) {
             const VariableReference& var = e.as<VariableReference>();
-            return var.fVariable->fModifiers.fLayout.fBuiltin == fBuiltin;
+            return var.fVariable->modifiers().fLayout.fBuiltin == fBuiltin;
         }
         return INHERITED::visitExpression(e);
     }
@@ -240,8 +240,8 @@
             case Expression::Kind::kVariableReference: {
                 VariableReference& varRef = expr.as<VariableReference>();
                 const Variable* var = varRef.fVariable;
-                if (var->fModifiers.fFlags & (Modifiers::kConst_Flag | Modifiers::kUniform_Flag |
-                                              Modifiers::kVarying_Flag)) {
+                if (var->modifiers().fFlags & (Modifiers::kConst_Flag | Modifiers::kUniform_Flag |
+                                               Modifiers::kVarying_Flag)) {
                     fErrors->error(expr.fOffset,
                                    "cannot modify immutable variable '" + var->name() + "'");
                 } else if (fAssignableVar) {
diff --git a/src/sksl/SkSLAnalysis.h b/src/sksl/SkSLAnalysis.h
index c606dc0..c6ce66f 100644
--- a/src/sksl/SkSLAnalysis.h
+++ b/src/sksl/SkSLAnalysis.h
@@ -21,7 +21,7 @@
 struct Program;
 struct ProgramElement;
 struct Statement;
-struct Variable;
+class Variable;
 struct VariableReference;
 
 /**
diff --git a/src/sksl/SkSLByteCodeGenerator.cpp b/src/sksl/SkSLByteCodeGenerator.cpp
index f71328d..dc5a406 100644
--- a/src/sksl/SkSLByteCodeGenerator.cpp
+++ b/src/sksl/SkSLByteCodeGenerator.cpp
@@ -117,11 +117,11 @@
 }
 
 static inline bool is_uniform(const SkSL::Variable& var) {
-    return var.fModifiers.fFlags & Modifiers::kUniform_Flag;
+    return var.modifiers().fFlags & Modifiers::kUniform_Flag;
 }
 
 static inline bool is_in(const SkSL::Variable& var) {
-    return var.fModifiers.fFlags & Modifiers::kIn_Flag;
+    return var.modifiers().fFlags & Modifiers::kIn_Flag;
 }
 
 void ByteCodeGenerator::gatherUniforms(const Type& type, const String& name) {
@@ -165,7 +165,7 @@
                 if (declVar->type() == *fContext.fFragmentProcessor_Type) {
                     fOutput->fChildFPCount++;
                 }
-                if (declVar->fModifiers.fLayout.fBuiltin >= 0 || is_in(*declVar)) {
+                if (declVar->modifiers().fLayout.fBuiltin >= 0 || is_in(*declVar)) {
                     continue;
                 }
                 if (is_uniform(*declVar)) {
@@ -218,8 +218,8 @@
 static int expression_as_builtin(const Expression& e) {
     if (e.is<VariableReference>()) {
         const Variable& var(*e.as<VariableReference>().fVariable);
-        if (var.fStorage == Variable::kGlobal_Storage) {
-            return var.fModifiers.fLayout.fBuiltin;
+        if (var.storage() == Variable::kGlobal_Storage) {
+            return var.modifiers().fLayout.fBuiltin;
         }
     }
     return -1;
@@ -423,7 +423,7 @@
 ByteCodeGenerator::Location ByteCodeGenerator::getLocation(const Variable& var) {
     // given that we seldom have more than a couple of variables, linear search is probably the most
     // efficient way to handle lookups
-    switch (var.fStorage) {
+    switch (var.storage()) {
         case Variable::kLocal_Storage: {
             for (int i = fLocals.size() - 1; i >= 0; --i) {
                 if (fLocals[i] == &var) {
@@ -486,7 +486,7 @@
                 if (e.is<GlobalVarDeclaration>()) {
                     const GlobalVarDeclaration& decl = e.as<GlobalVarDeclaration>();
                     const Variable* declVar = decl.fDecl->fVar;
-                    if (declVar->fModifiers.fLayout.fBuiltin >= 0 || is_in(*declVar)) {
+                    if (declVar->modifiers().fLayout.fBuiltin >= 0 || is_in(*declVar)) {
                         continue;
                     }
                     if (isUniform != is_uniform(*declVar)) {
@@ -1265,7 +1265,7 @@
     for (int i = 0; i < argCount; ++i) {
         const auto& param = f.function().fParameters[i];
         const auto& arg = f.arguments()[i];
-        if (param->fModifiers.fFlags & Modifiers::kOut_Flag) {
+        if (param->modifiers().fFlags & Modifiers::kOut_Flag) {
             lvalues.emplace_back(this->getLValue(*arg));
             lvalues.back()->load();
         } else {
@@ -1298,7 +1298,7 @@
     for (int i = argCount - 1; i >= 0; --i) {
         const auto& param = f.function().fParameters[i];
         const auto& arg = f.arguments()[i];
-        if (param->fModifiers.fFlags & Modifiers::kOut_Flag) {
+        if (param->modifiers().fFlags & Modifiers::kOut_Flag) {
             pop();
             lvalues.back()->store(true);
             lvalues.pop_back();
@@ -1834,7 +1834,7 @@
     fParameterCount = 0;
     for (const auto& p : declaration->fParameters) {
         int slots = ByteCodeGenerator::SlotCount(p->type());
-        fParameters.push_back({ slots, (bool)(p->fModifiers.fFlags & Modifiers::kOut_Flag) });
+        fParameters.push_back({ slots, (bool)(p->modifiers().fFlags & Modifiers::kOut_Flag) });
         fParameterCount += slots;
     }
 }
diff --git a/src/sksl/SkSLCPPCodeGenerator.cpp b/src/sksl/SkSLCPPCodeGenerator.cpp
index 77a3933..dd71973 100644
--- a/src/sksl/SkSLCPPCodeGenerator.cpp
+++ b/src/sksl/SkSLCPPCodeGenerator.cpp
@@ -20,8 +20,8 @@
 namespace SkSL {
 
 static bool needs_uniform_var(const Variable& var) {
-    return (var.fModifiers.fFlags & Modifiers::kUniform_Flag) &&
-           var.type().typeKind() != Type::TypeKind::kSampler;
+    return (var.modifiers().fFlags & Modifiers::kUniform_Flag) &&
+            var.type().typeKind() != Type::TypeKind::kSampler;
 }
 
 CPPCodeGenerator::CPPCodeGenerator(const Context* context, const Program* program,
@@ -128,22 +128,24 @@
 }
 
 static String default_value(const Variable& var) {
-    if (var.fModifiers.fLayout.fCType == SkSL::Layout::CType::kSkPMColor4f) {
+    if (var.modifiers().fLayout.fCType == SkSL::Layout::CType::kSkPMColor4f) {
         return "{SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}";
     }
     return default_value(var.type());
 }
 
 static bool is_private(const Variable& var) {
-    return !(var.fModifiers.fFlags & Modifiers::kUniform_Flag) &&
-           !(var.fModifiers.fFlags & Modifiers::kIn_Flag) &&
-           var.fStorage == Variable::kGlobal_Storage &&
-           var.fModifiers.fLayout.fBuiltin == -1;
+    const Modifiers& modifiers = var.modifiers();
+    return !(modifiers.fFlags & Modifiers::kUniform_Flag) &&
+           !(modifiers.fFlags & Modifiers::kIn_Flag) &&
+           var.storage() == Variable::kGlobal_Storage &&
+           modifiers.fLayout.fBuiltin == -1;
 }
 
 static bool is_uniform_in(const Variable& var) {
-    return (var.fModifiers.fFlags & Modifiers::kUniform_Flag) &&
-           (var.fModifiers.fFlags & Modifiers::kIn_Flag) &&
+    const Modifiers& modifiers = var.modifiers();
+    return (modifiers.fFlags & Modifiers::kUniform_Flag) &&
+           (modifiers.fFlags & Modifiers::kIn_Flag) &&
            var.type().typeKind() != Type::TypeKind::kSampler;
 }
 
@@ -260,7 +262,7 @@
 
 void CPPCodeGenerator::writeVarInitializer(const Variable& var, const Expression& value) {
     if (is_private(var)) {
-        this->writeRuntimeValue(var.type(), var.fModifiers.fLayout, var.name());
+        this->writeRuntimeValue(var.type(), var.modifiers().fLayout, var.name());
     } else {
         this->writeExpression(value, kTopLevel_Precedence);
     }
@@ -312,7 +314,7 @@
         this->write(ref.fVariable->name());
         return;
     }
-    switch (ref.fVariable->fModifiers.fLayout.fBuiltin) {
+    switch (ref.fVariable->modifiers().fLayout.fBuiltin) {
         case SK_OUTCOLOR_BUILTIN:
             this->write("%s");
             fFormatArgs.push_back(String("args.fOutputColor"));
@@ -336,13 +338,13 @@
                                       this->getSamplerHandle(*ref.fVariable) + ")");
                 return;
             }
-            if (ref.fVariable->fModifiers.fFlags & Modifiers::kUniform_Flag) {
+            if (ref.fVariable->modifiers().fFlags & Modifiers::kUniform_Flag) {
                 this->write("%s");
                 String name = ref.fVariable->name();
                 String var = String::printf("args.fUniformHandler->getUniformCStr(%sVar)",
                                             HCodeGenerator::FieldName(name.c_str()).c_str());
                 String code;
-                if (ref.fVariable->fModifiers.fLayout.fWhen.fLength) {
+                if (ref.fVariable->modifiers().fLayout.fWhen.fLength) {
                     code = String::printf("%sVar.isValid() ? %s : \"%s\"",
                                           HCodeGenerator::FieldName(name.c_str()).c_str(),
                                           var.c_str(),
@@ -353,7 +355,7 @@
                 fFormatArgs.push_back(code);
             } else if (SectionAndParameterHelper::IsParameter(*ref.fVariable)) {
                 String name(ref.fVariable->name());
-                this->writeRuntimeValue(ref.fVariable->type(), ref.fVariable->fModifiers.fLayout,
+                this->writeRuntimeValue(ref.fVariable->type(), ref.fVariable->modifiers().fLayout,
                                         String::printf("_outer.%s", name.c_str()).c_str());
             } else {
                 this->write(ref.fVariable->name());
@@ -657,8 +659,8 @@
         case ProgramElement::Kind::kGlobalVar: {
             const GlobalVarDeclaration& decl = p.as<GlobalVarDeclaration>();
             const Variable& var = *decl.fDecl->fVar;
-            if (var.fModifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kUniform_Flag) ||
-                -1 != var.fModifiers.fLayout.fBuiltin) {
+            if (var.modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kUniform_Flag) ||
+                -1 != var.modifiers().fLayout.fBuiltin) {
                 return;
             }
             break;
@@ -673,8 +675,8 @@
     if (!needs_uniform_var(var)) {
         return;
     }
-    if (var.fModifiers.fLayout.fWhen.fLength) {
-        this->writef("        if (%s) {\n    ", String(var.fModifiers.fLayout.fWhen).c_str());
+    if (var.modifiers().fLayout.fWhen.fLength) {
+        this->writef("        if (%s) {\n    ", String(var.modifiers().fLayout.fWhen).c_str());
     }
     String name(var.name());
     if (var.type().typeKind() != Type::TypeKind::kArray) {
@@ -691,7 +693,7 @@
                      name.c_str(),
                      var.type().columns());
     }
-    if (var.fModifiers.fLayout.fWhen.fLength) {
+    if (var.modifiers().fLayout.fWhen.fLength) {
         this->write("        }\n");
     }
 }
@@ -711,11 +713,11 @@
                 }
                 this->writef("%s %s = %s;\n",
                              HCodeGenerator::FieldType(fContext, decl.fVar->type(),
-                                                       decl.fVar->fModifiers.fLayout)
+                                                       decl.fVar->modifiers().fLayout)
                                      .c_str(),
                              String(decl.fVar->name()).c_str(),
                              default_value(*decl.fVar).c_str());
-            } else if (decl.fVar->fModifiers.fLayout.fFlags & Layout::kTracked_Flag) {
+            } else if (decl.fVar->modifiers().fLayout.fFlags & Layout::kTracked_Flag) {
                 // An auto-tracked uniform in variable, so add a field to hold onto the prior
                 // state. Note that tracked variables must be uniform in's and that is validated
                 // before writePrivateVars() is called.
@@ -1010,8 +1012,8 @@
             const char* name = nameString.c_str();
 
             // Switches for setData behavior in the generated code
-            bool conditionalUniform = u->fModifiers.fLayout.fWhen != "";
-            bool isTracked = u->fModifiers.fLayout.fFlags & Layout::kTracked_Flag;
+            bool conditionalUniform = u->modifiers().fLayout.fWhen != "";
+            bool isTracked = u->modifiers().fLayout.fFlags & Layout::kTracked_Flag;
             bool needsValueDeclaration = isTracked || !mapper->canInlineUniformValue();
 
             String uniformName = HCodeGenerator::FieldName(name) + "Var";
@@ -1029,7 +1031,7 @@
                 valueVar.appendf("%sValue", name);
                 // Use AccessType since that will match the return type of _outer's public API.
                 String valueType = HCodeGenerator::AccessType(fContext, u->type(),
-                                                              u->fModifiers.fLayout);
+                                                              u->modifiers().fLayout);
                 this->writef("%s%s %s = _outer.%s;\n",
                              indent.c_str(), valueType.c_str(), valueVar.c_str(), name);
             } else {
@@ -1189,7 +1191,7 @@
             // Add this field onto the format string and argument list.
             String fieldName = HCodeGenerator::FieldName(String(param->name()).c_str());
             String runtimeValue = this->formatRuntimeValue(param->type(),
-                                                           param->fModifiers.fLayout,
+                                                           param->modifiers().fLayout,
                                                            param->name(),
                                                            &argumentList);
             formatString.appendf("%s%s=%s",
@@ -1245,16 +1247,16 @@
             const Type& varType = var.type();
             String nameString(var.name());
             const char* name = nameString.c_str();
-            if (var.fModifiers.fLayout.fKey != Layout::kNo_Key &&
-                (var.fModifiers.fFlags & Modifiers::kUniform_Flag)) {
+            if (var.modifiers().fLayout.fKey != Layout::kNo_Key &&
+                (var.modifiers().fFlags & Modifiers::kUniform_Flag)) {
                 fErrors.error(var.fOffset, "layout(key) may not be specified on uniforms");
             }
-            switch (var.fModifiers.fLayout.fKey) {
+            switch (var.modifiers().fLayout.fKey) {
                 case Layout::kKey_Key:
                     if (is_private(var)) {
                         this->writef("%s %s =",
                                         HCodeGenerator::FieldType(fContext, varType,
-                                                                var.fModifiers.fLayout).c_str(),
+                                                                  var.modifiers().fLayout).c_str(),
                                         String(var.name()).c_str());
                         if (decl.fValue) {
                             fCPPMode = true;
@@ -1265,8 +1267,8 @@
                         }
                         this->write(";\n");
                     }
-                    if (var.fModifiers.fLayout.fWhen.fLength) {
-                        this->writef("if (%s) {", String(var.fModifiers.fLayout.fWhen).c_str());
+                    if (var.modifiers().fLayout.fWhen.fLength) {
+                        this->writef("if (%s) {", String(var.modifiers().fLayout.fWhen).c_str());
                     }
                     if (varType == *fContext.fHalf4_Type) {
                         this->writef("    uint16_t red = SkFloatToHalf(%s.fR);\n",
@@ -1291,7 +1293,7 @@
                         ABORT("NOT YET IMPLEMENTED: automatic key handling for %s\n",
                               varType.displayName().c_str());
                     }
-                    if (var.fModifiers.fLayout.fWhen.fLength) {
+                    if (var.modifiers().fLayout.fWhen.fLength) {
                         this->write("}");
                     }
                     break;
@@ -1315,7 +1317,7 @@
     for (const auto& p : fProgram) {
         if (p.is<GlobalVarDeclaration>()) {
             const VarDeclaration& decl = *p.as<GlobalVarDeclaration>().fDecl;
-            if ((decl.fVar->fModifiers.fFlags & Modifiers::kUniform_Flag) &&
+            if ((decl.fVar->modifiers().fFlags & Modifiers::kUniform_Flag) &&
                         decl.fVar->type().typeKind() != Type::TypeKind::kSampler) {
                 uniforms.push_back(decl.fVar);
             }
@@ -1330,7 +1332,7 @@
                             + "'s type is not supported for use as a 'uniform in'");
                     return false;
                 }
-                if (decl.fVar->fModifiers.fLayout.fFlags & Layout::kTracked_Flag) {
+                if (decl.fVar->modifiers().fLayout.fFlags & Layout::kTracked_Flag) {
                     if (!mapper->supportsTracking()) {
                         fErrors.error(decl.fOffset, String(decl.fVar->name())
                                 + "'s type does not support state tracking");
@@ -1340,7 +1342,7 @@
 
             } else {
                 // If it's not a uniform_in, it's an error to be tracked
-                if (decl.fVar->fModifiers.fLayout.fFlags & Layout::kTracked_Flag) {
+                if (decl.fVar->modifiers().fLayout.fFlags & Layout::kTracked_Flag) {
                     fErrors.error(decl.fOffset, "Non-'in uniforms' cannot be tracked");
                     return false;
                 }
@@ -1369,7 +1371,7 @@
     this->writeSetData(uniforms);
     this->writePrivateVars();
     for (const auto& u : uniforms) {
-        if (needs_uniform_var(*u) && !(u->fModifiers.fFlags & Modifiers::kIn_Flag)) {
+        if (needs_uniform_var(*u) && !(u->modifiers().fFlags & Modifiers::kIn_Flag)) {
             this->writef("    UniformHandle %sVar;\n",
                          HCodeGenerator::FieldName(String(u->name()).c_str()).c_str());
         }
diff --git a/src/sksl/SkSLCPPUniformCTypes.h b/src/sksl/SkSLCPPUniformCTypes.h
index f3455e9..1db23ac 100644
--- a/src/sksl/SkSLCPPUniformCTypes.h
+++ b/src/sksl/SkSLCPPUniformCTypes.h
@@ -50,7 +50,7 @@
                                          const Layout& layout);
 
     static const UniformCTypeMapper* Get(const Context& context, const Variable& variable) {
-        return Get(context, variable.type(), variable.fModifiers.fLayout);
+        return Get(context, variable.type(), variable.modifiers().fLayout);
     }
 
     // The C++ type name that this mapper applies to
diff --git a/src/sksl/SkSLCompiler.cpp b/src/sksl/SkSLCompiler.cpp
index 55f5c25..de6a241 100644
--- a/src/sksl/SkSLCompiler.cpp
+++ b/src/sksl/SkSLCompiler.cpp
@@ -250,8 +250,9 @@
     // treat it as builtin (ie, no need to clone it into the Program).
     StringFragment skCapsName("sk_Caps");
     fRootSymbolTable->add(skCapsName,
-                          std::make_unique<Variable>(/*offset=*/-1, Modifiers(), skCapsName,
-                                                     fContext->fSkCaps_Type.get(),
+                          std::make_unique<Variable>(/*offset=*/-1,
+                                                     fIRGenerator->fModifiers->handle(Modifiers()),
+                                                     skCapsName, fContext->fSkCaps_Type.get(),
                                                      /*builtin=*/false, Variable::kGlobal_Storage));
 
     fIRGenerator->fIntrinsics = nullptr;
@@ -266,22 +267,28 @@
                              &fragElements, &fFragmentSymbolTable);
 #else
     {
-        Rehydrator rehydrator(fContext.get(), fRootSymbolTable, this, SKSL_INCLUDE_sksl_gpu,
+        Rehydrator rehydrator(&fIRGenerator->fContext, fIRGenerator->fModifiers.get(),
+                              fRootSymbolTable, this, SKSL_INCLUDE_sksl_gpu,
                               SKSL_INCLUDE_sksl_gpu_LENGTH);
         fGpuSymbolTable = rehydrator.symbolTable();
         gpuElements = rehydrator.elements();
+        fModifiers.push_back(fIRGenerator->releaseModifiers());
     }
     {
-        Rehydrator rehydrator(fContext.get(), fGpuSymbolTable, this, SKSL_INCLUDE_sksl_vert,
+        Rehydrator rehydrator(&fIRGenerator->fContext, fIRGenerator->fModifiers.get(),
+                              fGpuSymbolTable, this, SKSL_INCLUDE_sksl_vert,
                               SKSL_INCLUDE_sksl_vert_LENGTH);
         fVertexSymbolTable = rehydrator.symbolTable();
         fVertexInclude = rehydrator.elements();
+        fModifiers.push_back(fIRGenerator->releaseModifiers());
     }
     {
-        Rehydrator rehydrator(fContext.get(), fGpuSymbolTable, this, SKSL_INCLUDE_sksl_frag,
+        Rehydrator rehydrator(&fIRGenerator->fContext, fIRGenerator->fModifiers.get(),
+                              fGpuSymbolTable, this, SKSL_INCLUDE_sksl_frag,
                               SKSL_INCLUDE_sksl_frag_LENGTH);
         fFragmentSymbolTable = rehydrator.symbolTable();
         fragElements = rehydrator.elements();
+        fModifiers.push_back(fIRGenerator->releaseModifiers());
     }
 #endif
     // Call counts are used to track dead-stripping and inlinability within the program being
@@ -308,10 +315,12 @@
     }
     #if !SKSL_STANDALONE
         {
-            Rehydrator rehydrator(fContext.get(), fGpuSymbolTable, this, SKSL_INCLUDE_sksl_geom,
-                              SKSL_INCLUDE_sksl_geom_LENGTH);
+            Rehydrator rehydrator(&fIRGenerator->fContext, fIRGenerator->fModifiers.get(),
+                                  fGpuSymbolTable, this, SKSL_INCLUDE_sksl_geom,
+                                  SKSL_INCLUDE_sksl_geom_LENGTH);
             fGeometrySymbolTable = rehydrator.symbolTable();
             fGeometryInclude = rehydrator.elements();
+            fModifiers.push_back(fIRGenerator->releaseModifiers());
         }
     #else
         this->processIncludeFile(Program::kGeometry_Kind, SKSL_GEOM_INCLUDE, fGpuSymbolTable,
@@ -327,7 +336,8 @@
     std::vector<std::unique_ptr<ProgramElement>> fpElements;
     #if !SKSL_STANDALONE
         {
-            Rehydrator rehydrator(fContext.get(), fGpuSymbolTable, this, SKSL_INCLUDE_sksl_fp,
+            Rehydrator rehydrator(&fIRGenerator->fContext, fIRGenerator->fModifiers.get(),
+                                  fGpuSymbolTable, this, SKSL_INCLUDE_sksl_fp,
                                   SKSL_INCLUDE_sksl_fp_LENGTH);
             fFPSymbolTable = rehydrator.symbolTable();
             fpElements = rehydrator.elements();
@@ -347,11 +357,12 @@
     std::vector<std::unique_ptr<ProgramElement>> pipelineIntrinics;
     #if !SKSL_STANDALONE
         {
-            Rehydrator rehydrator(fContext.get(), fGpuSymbolTable, this,
-                                  SKSL_INCLUDE_sksl_pipeline,
+            Rehydrator rehydrator(&fIRGenerator->fContext, fIRGenerator->fModifiers.get(),
+                                  fGpuSymbolTable, this, SKSL_INCLUDE_sksl_pipeline,
                                   SKSL_INCLUDE_sksl_pipeline_LENGTH);
             fPipelineSymbolTable = rehydrator.symbolTable();
             pipelineIntrinics = rehydrator.elements();
+            fModifiers.push_back(fIRGenerator->releaseModifiers());
         }
     #else
         this->processIncludeFile(Program::kPipelineStage_Kind, SKSL_PIPELINE_INCLUDE,
@@ -368,11 +379,12 @@
     std::vector<std::unique_ptr<ProgramElement>> interpElements;
     #if !SKSL_STANDALONE
         {
-            Rehydrator rehydrator(fContext.get(), fRootSymbolTable, this,
-                                  SKSL_INCLUDE_sksl_interp,
+            Rehydrator rehydrator(&fIRGenerator->fContext, fIRGenerator->fModifiers.get(),
+                                  fRootSymbolTable, this, SKSL_INCLUDE_sksl_interp,
                                   SKSL_INCLUDE_sksl_interp_LENGTH);
             fInterpreterSymbolTable = rehydrator.symbolTable();
             interpElements = rehydrator.elements();
+            fModifiers.push_back(fIRGenerator->releaseModifiers());
         }
     #else
         this->processIncludeFile(Program::kGeneric_Kind, SKSL_INTERP_INCLUDE,
@@ -414,6 +426,7 @@
 #ifdef SK_DEBUG
     fSource = nullptr;
 #endif
+    fModifiers.push_back(fIRGenerator->releaseModifiers());
     fIRGenerator->finish();
 }
 
@@ -423,7 +436,7 @@
     switch (lvalue->kind()) {
         case Expression::Kind::kVariableReference: {
             const Variable& var = *lvalue->as<VariableReference>().fVariable;
-            if (var.fStorage == Variable::kLocal_Storage) {
+            if (var.storage() == Variable::kLocal_Storage) {
                 (*definitions)[&var] = expr;
             }
             break;
@@ -492,7 +505,7 @@
             case Expression::Kind::kFunctionCall: {
                 const FunctionCall& c = expr->as<FunctionCall>();
                 for (size_t i = 0; i < c.function().fParameters.size(); ++i) {
-                    if (c.function().fParameters[i]->fModifiers.fFlags & Modifiers::kOut_Flag) {
+                    if (c.function().fParameters[i]->modifiers().fFlags & Modifiers::kOut_Flag) {
                         this->addDefinition(
                                   c.arguments()[i].get(),
                                   (std::unique_ptr<Expression>*) &fContext->fDefined_Expression,
@@ -905,7 +918,7 @@
             const Variable* var = ref.fVariable;
             if (ref.refKind() != VariableReference::kWrite_RefKind &&
                 ref.refKind() != VariableReference::kPointer_RefKind &&
-                var->fStorage == Variable::kLocal_Storage && !definitions[var] &&
+                var->storage() == Variable::kLocal_Storage && !definitions[var] &&
                 (*undefinedVariables).find(var) == (*undefinedVariables).end()) {
                 (*undefinedVariables).insert(var);
                 this->error(expr->fOffset,
@@ -1572,7 +1585,7 @@
 
     fErrorText = "";
     fErrorCount = 0;
-    fInliner.reset(context(), settings);
+    fInliner.reset(&fIRGenerator->fContext, fIRGenerator->fModifiers.get(), &settings);
     std::vector<std::unique_ptr<ProgramElement>>* inherited;
     std::vector<std::unique_ptr<ProgramElement>> elements;
     switch (kind) {
@@ -1627,6 +1640,7 @@
                                             fContext,
                                             inherited,
                                             std::move(elements),
+                                            fIRGenerator->releaseModifiers(),
                                             fIRGenerator->fSymbolTable,
                                             fIRGenerator->fInputs);
     fIRGenerator->finish();
@@ -1696,6 +1710,7 @@
             break;
         }
     }
+    program.finish();
     return fErrorCount == 0;
 }
 
@@ -1705,7 +1720,8 @@
 #ifdef SK_ENABLE_SPIRV_VALIDATION
     StringStream buffer;
     fSource = program.fSource.get();
-    SPIRVCodeGenerator cg(fContext.get(), &program, this, &buffer);
+    SPIRVCodeGenerator cg(&fIRGenerator->fContext, fIRGenerator->fModifiers.get(), &program, this,
+                          &buffer);
     bool result = cg.generateCode();
     fSource = nullptr;
     if (result) {
@@ -1723,7 +1739,8 @@
     }
 #else
     fSource = program.fSource.get();
-    SPIRVCodeGenerator cg(fContext.get(), &program, this, &out);
+    SPIRVCodeGenerator cg(&fIRGenerator->fContext, fIRGenerator->fModifiers.get(), &program, this,
+                          &out);
     bool result = cg.generateCode();
     fSource = nullptr;
 #endif
diff --git a/src/sksl/SkSLCompiler.h b/src/sksl/SkSLCompiler.h
index 6de2480..1a18b77 100644
--- a/src/sksl/SkSLCompiler.h
+++ b/src/sksl/SkSLCompiler.h
@@ -252,6 +252,9 @@
     std::shared_ptr<SymbolTable> fFPSymbolTable;
     std::unique_ptr<IRIntrinsicMap> fFPIntrinsics;
 
+    // holds ModifiersPools belonging to the core includes for lifetime purposes
+    std::vector<std::unique_ptr<ModifiersPool>> fModifiers;
+
     Inliner fInliner;
     std::unique_ptr<IRGenerator> fIRGenerator;
     int fFlags;
diff --git a/src/sksl/SkSLDehydrator.cpp b/src/sksl/SkSLDehydrator.cpp
index c2619ec..b67bf65 100644
--- a/src/sksl/SkSLDehydrator.cpp
+++ b/src/sksl/SkSLDehydrator.cpp
@@ -216,10 +216,10 @@
             const Variable& v = s.as<Variable>();
             this->writeU8(Rehydrator::kVariable_Command);
             this->writeId(&v);
-            this->write(v.fModifiers);
+            this->write(v.modifiers());
             this->write(v.name());
             this->write(v.type());
-            this->writeU8(v.fStorage);
+            this->writeU8(v.storage());
             break;
         }
         case Symbol::Kind::kField: {
@@ -519,8 +519,8 @@
             for (const std::unique_ptr<const Symbol>& s : en.symbols()->fOwnedSymbols) {
                 SkASSERT(s->kind() == Symbol::Kind::kVariable);
                 Variable& v = (Variable&) *s;
-                SkASSERT(v.fInitialValue);
-                const IntLiteral& i = v.fInitialValue->as<IntLiteral>();
+                SkASSERT(v.initialValue());
+                const IntLiteral& i = v.initialValue()->as<IntLiteral>();
                 this->writeS32(i.value());
             }
             break;
diff --git a/src/sksl/SkSLGLSLCodeGenerator.cpp b/src/sksl/SkSLGLSLCodeGenerator.cpp
index 66452fd..9541eb9 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.cpp
+++ b/src/sksl/SkSLGLSLCodeGenerator.cpp
@@ -785,7 +785,7 @@
 }
 
 void GLSLCodeGenerator::writeVariableReference(const VariableReference& ref) {
-    switch (ref.fVariable->fModifiers.fLayout.fBuiltin) {
+    switch (ref.fVariable->modifiers().fLayout.fBuiltin) {
         case SK_FRAGCOLOR_BUILTIN:
             if (fProgram.fSettings.fCaps->mustDeclareFragmentShaderOutput()) {
                 this->write("sk_FragColor");
@@ -1050,7 +1050,7 @@
     for (const auto& param : f.fDeclaration.fParameters) {
         this->write(separator);
         separator = ", ";
-        this->writeModifiers(param->fModifiers, false);
+        this->writeModifiers(param->modifiers(), false);
         std::vector<int> sizes;
         const Type* type = &param->type();
         while (type->typeKind() == Type::TypeKind::kArray) {
@@ -1177,7 +1177,7 @@
     if (intf.fTypeName == "sk_PerVertex") {
         return;
     }
-    this->writeModifiers(intf.fVariable.fModifiers, true);
+    this->writeModifiers(intf.fVariable.modifiers(), true);
     this->writeLine(intf.fTypeName + " {");
     fIndentation++;
     const Type* structType = &intf.fVariable.type();
@@ -1245,7 +1245,7 @@
 }
 
 void GLSLCodeGenerator::writeVarDeclaration(const VarDeclaration& var, bool global) {
-    this->writeModifiers(var.fVar->fModifiers, global);
+    this->writeModifiers(var.fVar->modifiers(), global);
     this->writeTypePrecision(var.fBaseType);
     this->writeType(var.fBaseType);
     this->write(" ");
@@ -1487,14 +1487,14 @@
             break;
         case ProgramElement::Kind::kGlobalVar: {
             const VarDeclaration& decl = *e.as<GlobalVarDeclaration>().fDecl;
-            int builtin = decl.fVar->fModifiers.fLayout.fBuiltin;
+            int builtin = decl.fVar->modifiers().fLayout.fBuiltin;
             if (builtin == -1) {
                 // normal var
                 this->writeVarDeclaration(decl, true);
                 this->writeLine();
             } else if (builtin == SK_FRAGCOLOR_BUILTIN &&
                        fProgram.fSettings.fCaps->mustDeclareFragmentShaderOutput() &&
-                       decl.fVar->fWriteCount) {
+                       decl.fVar->writeCount()) {
                 if (fProgram.fSettings.fFragColorIsInOut) {
                     this->write("inout ");
                 } else {
diff --git a/src/sksl/SkSLHCodeGenerator.cpp b/src/sksl/SkSLHCodeGenerator.cpp
index 291b8dc..fb4ffe6 100644
--- a/src/sksl/SkSLHCodeGenerator.cpp
+++ b/src/sksl/SkSLHCodeGenerator.cpp
@@ -190,7 +190,7 @@
         separator = "";
         for (const auto& param : fSectionAndParameterHelper.getParameters()) {
             this->writef("%s%s %s", separator, ParameterType(fContext, param->type(),
-                                                             param->fModifiers.fLayout).c_str(),
+                                                             param->modifiers().fLayout).c_str(),
                          String(param->name()).c_str());
             separator = ", ";
         }
@@ -234,7 +234,7 @@
     const char* separator = "";
     for (const auto& param : fSectionAndParameterHelper.getParameters()) {
         this->writef("%s%s %s", separator, ParameterType(fContext, param->type(),
-                                                         param->fModifiers.fLayout).c_str(),
+                                                         param->modifiers().fLayout).c_str(),
                      String(param->name()).c_str());
         separator = ", ";
     }
@@ -287,7 +287,7 @@
             std::string perspExpression;
             if (usage.hasUniformMatrix()) {
                 for (const Variable* p : fSectionAndParameterHelper.getParameters()) {
-                    if ((p->fModifiers.fFlags & Modifiers::kIn_Flag) &&
+                    if ((p->modifiers().fFlags & Modifiers::kIn_Flag) &&
                         usage.fExpression == String(p->name())) {
                         perspExpression = usage.fExpression + ".hasPerspective()";
                         break;
@@ -315,7 +315,7 @@
             // Don't need to write any fields, FPs are held as children
         } else {
             this->writef("    %s %s;\n", FieldType(fContext, param->type(),
-                                                   param->fModifiers.fLayout).c_str(),
+                                                   param->modifiers().fLayout).c_str(),
                                          name.c_str());
         }
     }
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index e883af8..fc6bd2d 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -127,7 +127,8 @@
         , fSymbolTable(symbolTable)
         , fLoopLevel(0)
         , fSwitchLevel(0)
-        , fErrors(errorReporter) {
+        , fErrors(errorReporter)
+        , fModifiers(new ModifiersPool()) {
     SkASSERT(fInliner);
 }
 
@@ -211,6 +212,14 @@
 void IRGenerator::finish() {
     this->popSymbolTable();
     fSettings = nullptr;
+    // releaseModifiers should have been called before now
+    SkASSERT(fModifiers->empty());
+}
+
+std::unique_ptr<ModifiersPool> IRGenerator::releaseModifiers() {
+    std::unique_ptr<ModifiersPool> result = std::move(fModifiers);
+    fModifiers = std::make_unique<ModifiersPool>();
+    return result;
 }
 
 std::unique_ptr<Statement> IRGenerator::convertSingleStatement(const ASTNode& statement) {
@@ -444,8 +453,8 @@
                 sizes.push_back(nullptr);
             }
         }
-        auto var = std::make_unique<Variable>(varDecl.fOffset, modifiers, varData.fName, type,
-                                              fIsBuiltinCode, storage);
+        auto var = std::make_unique<Variable>(varDecl.fOffset, fModifiers->handle(modifiers),
+                                              varData.fName, type, fIsBuiltinCode, storage);
         if (var->name() == Compiler::RTADJUST_NAME) {
             SkASSERT(!fRTAdjust);
             SkASSERT(var->type() == *fContext.fFloat4_Type);
@@ -461,17 +470,16 @@
             if (!value) {
                 return {};
             }
-            var->fWriteCount = 1;
-            var->fInitialValue = value.get();
+            var->setInitialValue(value.get());
         }
-        const Symbol* symbol = (*fSymbolTable)[var->name()];
+        Symbol* symbol = (*fSymbolTable)[var->name()];
         if (symbol && storage == Variable::kGlobal_Storage && var->name() == "sk_FragColor") {
             // Already defined, ignore.
         } else if (symbol && storage == Variable::kGlobal_Storage &&
                    symbol->kind() == Symbol::Kind::kVariable &&
-                   symbol->as<Variable>().fModifiers.fLayout.fBuiltin >= 0) {
+                   symbol->as<Variable>().modifiers().fLayout.fBuiltin >= 0) {
             // Already defined, just update the modifiers.
-            symbol->as<Variable>().fModifiers = var->fModifiers;
+            symbol->as<Variable>().setModifiersHandle(var->modifiersHandle());
         } else {
             varDecls.emplace_back(std::make_unique<VarDeclaration>(
                     var.get(), baseType, std::move(sizes), std::move(value)));
@@ -500,9 +508,11 @@
         fInvocations = modifiers.fLayout.fInvocations;
         if (fSettings->fCaps && !fSettings->fCaps->gsInvocationsSupport()) {
             modifiers.fLayout.fInvocations = -1;
-            const Variable& invocationId = (*fSymbolTable)["sk_InvocationID"]->as<Variable>();
-            invocationId.fModifiers.fFlags = 0;
-            invocationId.fModifiers.fLayout.fBuiltin = -1;
+            Variable& invocationId = (*fSymbolTable)["sk_InvocationID"]->as<Variable>();
+            Modifiers modifiers = invocationId.modifiers();
+            modifiers.fFlags = 0;
+            modifiers.fLayout.fBuiltin = -1;
+            invocationId.setModifiersHandle(fModifiers->handle(modifiers));
             if (modifiers.fLayout.description() == "") {
                 return nullptr;
             }
@@ -768,7 +778,7 @@
             "_invoke", std::make_unique<FunctionDeclaration>(/*offset=*/-1,
                                                              invokeModifiers,
                                                              "_invoke",
-                                                             std::vector<const Variable*>(),
+                                                             std::vector<Variable*>(),
                                                              *fContext.fVoid_Type,
                                                              /*builtin=*/false));
     fProgramElements->push_back(std::make_unique<FunctionDefinition>(/*offset=*/-1,
@@ -930,7 +940,7 @@
     const ASTNode::FunctionData& funcData = f.getFunctionData();
     this->checkModifiers(f.fOffset, funcData.fModifiers, Modifiers::kHasSideEffects_Flag |
                                                          Modifiers::kInline_Flag);
-    std::vector<const Variable*> parameters;
+    std::vector<Variable*> parameters;
     for (size_t i = 0; i < funcData.fParameterCount; ++i) {
         const ASTNode& param = *(iter++);
         SkASSERT(param.fKind == ASTNode::Kind::kParameter);
@@ -956,15 +966,16 @@
             return;
         }
         StringFragment name = pd.fName;
-        const Variable* var = fSymbolTable->takeOwnershipOfSymbol(
-                std::make_unique<Variable>(param.fOffset, pd.fModifiers, name, type,
-                                           fIsBuiltinCode, Variable::kParameter_Storage));
+        Variable* var = fSymbolTable->takeOwnershipOfSymbol(
+                std::make_unique<Variable>(param.fOffset, fModifiers->handle(pd.fModifiers),
+                                           name, type, fIsBuiltinCode,
+                                           Variable::kParameter_Storage));
         parameters.push_back(var);
     }
 
     auto paramIsCoords = [&](int idx) {
         return parameters[idx]->type() == *fContext.fFloat2_Type &&
-               parameters[idx]->fModifiers.fFlags == 0;
+               parameters[idx]->modifiers().fFlags == 0;
     };
 
     if (funcData.fName == "main") {
@@ -1036,7 +1047,7 @@
                     }
                     decl = other;
                     for (size_t i = 0; i < parameters.size(); i++) {
-                        if (parameters[i]->fModifiers != other->fParameters[i]->fModifiers) {
+                        if (parameters[i]->modifiers() != other->fParameters[i]->modifiers()) {
                             fErrors.error(f.fOffset, "modifiers on parameter " +
                                                      to_string((uint64_t) i + 1) +
                                                      " differ between declaration and definition");
@@ -1077,7 +1088,9 @@
                                          fKind == Program::kFragmentProcessor_Kind)) {
             if (parameters.size() == 1) {
                 SkASSERT(paramIsCoords(0));
-                parameters[0]->fModifiers.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN;
+                Modifiers m = parameters[0]->modifiers();
+                m.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN;
+                parameters[0]->setModifiersHandle(fModifiers->handle(m));
             }
         }
         for (size_t i = 0; i < parameters.size(); i++) {
@@ -1140,7 +1153,7 @@
                 SkASSERT(vd.fVar->type() == *fContext.fFloat4_Type);
                 fRTAdjustFieldIndex = fields.size();
             }
-            fields.push_back(Type::Field(vd.fVar->fModifiers, vd.fVar->name(),
+            fields.push_back(Type::Field(vd.fVar->modifiers(), vd.fVar->name(),
                                         &vd.fVar->type()));
             if (vd.fValue) {
                 fErrors.error(decl->fOffset,
@@ -1186,9 +1199,9 @@
             sizes.push_back(nullptr);
         }
     }
-    const Variable* var = old->takeOwnershipOfSymbol(
+    Variable* var = old->takeOwnershipOfSymbol(
             std::make_unique<Variable>(intf.fOffset,
-                                       id.fModifiers,
+                                       fModifiers->handle(id.fModifiers),
                                        id.fInstanceName.fLength ? id.fInstanceName : id.fTypeName,
                                        type,
                                        fIsBuiltinCode,
@@ -1218,9 +1231,8 @@
             return true;
         case Expression::Kind::kVariableReference: {
             const Variable& var = *value.as<VariableReference>().fVariable;
-            return (var.fModifiers.fFlags & Modifiers::kConst_Flag) &&
-                   var.fInitialValue &&
-                   this->getConstantInt(*var.fInitialValue, out);
+            return (var.modifiers().fFlags & Modifiers::kConst_Flag) &&
+                   var.initialValue() && this->getConstantInt(*var.initialValue(), out);
         }
         default:
             return false;
@@ -1262,8 +1274,9 @@
         ++currentValue;
         fSymbolTable->add(
                 child.getString(),
-                std::make_unique<Variable>(e.fOffset, modifiers, child.getString(), type,
-                                           fIsBuiltinCode, Variable::kGlobal_Storage, value.get()));
+                std::make_unique<Variable>(e.fOffset, fModifiers->handle(modifiers),
+                                           child.getString(), type, fIsBuiltinCode,
+                                           Variable::kGlobal_Storage, value.get()));
         fSymbolTable->takeOwnershipOfIRNode(std::move(value));
     }
     // Now we orphanize the Enum's symbol table, so that future lookups in it are strict
@@ -1376,7 +1389,8 @@
         }
         case Symbol::Kind::kVariable: {
             const Variable* var = &result->as<Variable>();
-            switch (var->fModifiers.fLayout.fBuiltin) {
+            const Modifiers& modifiers = var->modifiers();
+            switch (modifiers.fLayout.fBuiltin) {
                 case SK_WIDTH_BUILTIN:
                     fInputs.fRTWidth = true;
                     break;
@@ -1394,10 +1408,10 @@
 #endif
             }
             if (fKind == Program::kFragmentProcessor_Kind &&
-                (var->fModifiers.fFlags & Modifiers::kIn_Flag) &&
-                !(var->fModifiers.fFlags & Modifiers::kUniform_Flag) &&
-                !var->fModifiers.fLayout.fKey &&
-                var->fModifiers.fLayout.fBuiltin == -1 &&
+                (modifiers.fFlags & Modifiers::kIn_Flag) &&
+                !(modifiers.fFlags & Modifiers::kUniform_Flag) &&
+                !modifiers.fLayout.fKey &&
+                modifiers.fLayout.fBuiltin == -1 &&
                 var->type().nonnullable() != *fContext.fFragmentProcessor_Type &&
                 var->type().typeKind() != Type::TypeKind::kSampler) {
                 bool valid = false;
@@ -1454,7 +1468,6 @@
                                                 section.fText);
 }
 
-
 std::unique_ptr<Expression> IRGenerator::coerce(std::unique_ptr<Expression> expr,
                                                 const Type& type) {
     if (!expr) {
@@ -2109,7 +2122,7 @@
         if (!arguments[i]) {
             return nullptr;
         }
-        const Modifiers& paramModifiers = function.fParameters[i]->fModifiers;
+        const Modifiers& paramModifiers = function.fParameters[i]->modifiers();
         if (paramModifiers.fFlags & Modifiers::kOut_Flag) {
             if (!this->setRefKind(*arguments[i], paramModifiers.fFlags & Modifiers::kIn_Flag
                                                          ? VariableReference::kReadWrite_RefKind
@@ -2697,9 +2710,9 @@
                 ASTNode(&fFile->fNodes, offset, ASTNode::Kind::kIdentifier, field));
         if (result) {
             const Variable& v = *result->as<VariableReference>().fVariable;
-            SkASSERT(v.fInitialValue);
+            SkASSERT(v.initialValue());
             result = std::make_unique<IntLiteral>(
-                    offset, v.fInitialValue->as<IntLiteral>().value(), &type);
+                    offset, v.initialValue()->as<IntLiteral>().value(), &type);
         } else {
             fErrors.error(offset,
                           "type '" + type.name() + "' does not have a member named '" + field +
@@ -2860,8 +2873,8 @@
                 // so we're pointing at a Program-owned expression.
                 const Variable* clonedVar =
                         fGenerator->fSymbolTable->takeOwnershipOfSymbol(std::make_unique<Variable>(
-                                sharedVar->fOffset, sharedVar->fModifiers, sharedVar->name(),
-                                &sharedVar->type(), /*builtin=*/false, sharedVar->fStorage,
+                                sharedVar->fOffset, sharedVar->modifiersHandle(), sharedVar->name(),
+                                &sharedVar->type(), /*builtin=*/false, sharedVar->storage(),
                                 varDecl.fValue.get()));
 
                 // Go back and update the VarDeclaration to point at the cloned Variable.
@@ -2877,7 +2890,7 @@
 
         bool visitExpression(Expression& e) override {
             // Look for references to builtin variables.
-            if (e.is<VariableReference>() && e.as<VariableReference>().fVariable->fBuiltin) {
+            if (e.is<VariableReference>() && e.as<VariableReference>().fVariable->isBuiltin()) {
                 const Variable* sharedVar = e.as<VariableReference>().fVariable;
 
                 this->cloneVariable(sharedVar->name());
diff --git a/src/sksl/SkSLIRGenerator.h b/src/sksl/SkSLIRGenerator.h
index 9ba9e77..5e60f70 100644
--- a/src/sksl/SkSLIRGenerator.h
+++ b/src/sksl/SkSLIRGenerator.h
@@ -123,6 +123,11 @@
      */
     void finish();
 
+    /**
+     * Relinquishes ownership of the Modifiers that have been collected so far and returns them.
+     */
+    std::unique_ptr<ModifiersPool> releaseModifiers();
+
     void pushSymbolTable();
     void popSymbolTable();
 
@@ -224,6 +229,7 @@
     bool fCanInline = true;
     // true if we are currently processing one of the built-in SkSL include files
     bool fIsBuiltinCode;
+    std::unique_ptr<ModifiersPool> fModifiers;
 
     friend class AutoSymbolTable;
     friend class AutoLoopLevel;
diff --git a/src/sksl/SkSLInliner.cpp b/src/sksl/SkSLInliner.cpp
index 5b3fb4e..1755bdf 100644
--- a/src/sksl/SkSLInliner.cpp
+++ b/src/sksl/SkSLInliner.cpp
@@ -301,9 +301,11 @@
     }
 }
 
-void Inliner::reset(const Context& context, const Program::Settings& settings) {
-    fContext = &context;
-    fSettings = &settings;
+void Inliner::reset(const Context* context, ModifiersPool* modifiers,
+                    const Program::Settings* settings) {
+    fContext = context;
+    fModifiers = modifiers;
+    fSettings = settings;
     fInlineVarCounter = 0;
 }
 
@@ -550,11 +552,11 @@
             const Type* typePtr = copy_if_needed(&old->type(), *symbolTableForStatement);
             const Variable* clone = symbolTableForStatement->takeOwnershipOfSymbol(
                     std::make_unique<Variable>(offset,
-                                               old->fModifiers,
+                                               old->modifiersHandle(),
                                                namePtr->c_str(),
                                                typePtr,
                                                isBuiltinCode,
-                                               old->fStorage,
+                                               old->storage(),
                                                initialValue.get()));
             (*varMap)[old] = std::make_unique<VariableReference>(offset, clone);
             return std::make_unique<VarDeclaration>(clone, baseTypePtr, std::move(sizes),
@@ -628,9 +630,10 @@
         StringFragment nameFrag{namePtr->c_str(), namePtr->length()};
 
         // Add our new variable to the symbol table.
-        auto newVar = std::make_unique<Variable>(/*offset=*/-1, Modifiers(), nameFrag, type,
-                                                 caller->fBuiltin, Variable::kLocal_Storage,
-                                                 initialValue->get());
+        auto newVar = std::make_unique<Variable>(/*offset=*/-1,
+                                                 fModifiers->handle(Modifiers()),
+                                                 nameFrag, type, caller->fBuiltin,
+                                                 Variable::kLocal_Storage, initialValue->get());
         const Variable* variableSymbol = symbolTableForCall->add(nameFrag, std::move(newVar));
 
         // Prepare the variable declaration (taking extra care with `out` params to not clobber any
@@ -667,7 +670,7 @@
     std::vector<int> argsToCopyBack;
     for (int i = 0; i < (int) arguments.size(); ++i) {
         const Variable* param = function.fDeclaration.fParameters[i];
-        bool isOutParam = param->fModifiers.fFlags & Modifiers::kOut_Flag;
+        bool isOutParam = param->modifiers().fFlags & Modifiers::kOut_Flag;
 
         // If this argument can be inlined trivially (e.g. a swizzle, or a constant array index)...
         if (is_trivial_argument(*arguments[i])) {
@@ -684,7 +687,7 @@
         }
 
         varMap[param] = makeInlineVar(String(param->name()), &arguments[i]->type(),
-                                      param->fModifiers, &arguments[i]);
+                                      param->modifiers(), &arguments[i]);
     }
 
     const Block& body = function.fBody->as<Block>();
@@ -730,7 +733,8 @@
     } else {
         // It's a void function, so it doesn't actually result in anything, but we have to return
         // something non-null as a standin.
-        inlinedCall.fReplacementExpr = std::make_unique<BoolLiteral>(*fContext, offset,
+        inlinedCall.fReplacementExpr = std::make_unique<BoolLiteral>(*fContext,
+                                                                     offset,
                                                                      /*value=*/false);
     }
 
diff --git a/src/sksl/SkSLInliner.h b/src/sksl/SkSLInliner.h
index 7a723b7..8f9d382 100644
--- a/src/sksl/SkSLInliner.h
+++ b/src/sksl/SkSLInliner.h
@@ -23,9 +23,10 @@
 struct FunctionDefinition;
 struct InlineCandidate;
 struct InlineCandidateList;
+class ModifiersPool;
 struct Statement;
 class SymbolTable;
-struct Variable;
+class Variable;
 
 /**
  * Converts a FunctionCall in the IR to a set of statements to be injected ahead of the function
@@ -37,7 +38,7 @@
 public:
     Inliner() {}
 
-    void reset(const Context&, const Program::Settings&);
+    void reset(const Context*, ModifiersPool* modifiers, const Program::Settings*);
 
     /**
      * Processes the passed-in FunctionCall expression. The FunctionCall expression should be
@@ -87,6 +88,7 @@
     bool isLargeFunction(const InlineCandidate& candidate, LargeFunctionCache* cache);
 
     const Context* fContext = nullptr;
+    ModifiersPool* fModifiers = nullptr;
     const Program::Settings* fSettings = nullptr;
     int fInlineVarCounter = 0;
 };
diff --git a/src/sksl/SkSLMetalCodeGenerator.cpp b/src/sksl/SkSLMetalCodeGenerator.cpp
index 2ef5451..8dfb0f9 100644
--- a/src/sksl/SkSLMetalCodeGenerator.cpp
+++ b/src/sksl/SkSLMetalCodeGenerator.cpp
@@ -265,7 +265,7 @@
         const Expression& arg = *arguments[i];
         this->write(separator);
         separator = ", ";
-        if (function.fParameters[i]->fModifiers.fFlags & Modifiers::kOut_Flag) {
+        if (function.fParameters[i]->modifiers().fFlags & Modifiers::kOut_Flag) {
             this->write("&");
         }
         this->writeExpression(arg, kSequence_Precedence);
@@ -680,7 +680,7 @@
 }
 
 void MetalCodeGenerator::writeVariableReference(const VariableReference& ref) {
-    switch (ref.fVariable->fModifiers.fLayout.fBuiltin) {
+    switch (ref.fVariable->modifiers().fLayout.fBuiltin) {
         case SK_FRAGCOLOR_BUILTIN:
             this->write("_out->sk_FragColor");
             break;
@@ -699,12 +699,12 @@
             this->write(fProgram.fSettings.fFlipY ? "_frontFacing" : "(!_frontFacing)");
             break;
         default:
-            if (Variable::kGlobal_Storage == ref.fVariable->fStorage) {
-                if (ref.fVariable->fModifiers.fFlags & Modifiers::kIn_Flag) {
+            if (Variable::kGlobal_Storage == ref.fVariable->storage()) {
+                if (ref.fVariable->modifiers().fFlags & Modifiers::kIn_Flag) {
                     this->write("_in.");
-                } else if (ref.fVariable->fModifiers.fFlags & Modifiers::kOut_Flag) {
+                } else if (ref.fVariable->modifiers().fFlags & Modifiers::kOut_Flag) {
                     this->write("_out->");
-                } else if (ref.fVariable->fModifiers.fFlags & Modifiers::kUniform_Flag &&
+                } else if (ref.fVariable->modifiers().fFlags & Modifiers::kUniform_Flag &&
                            ref.fVariable->type().typeKind() != Type::TypeKind::kSampler) {
                     this->write("_uniforms.");
                 } else {
@@ -836,8 +836,8 @@
         this->write("(");
     }
     if (Compiler::IsAssignment(op) && left.is<VariableReference>() &&
-        left.as<VariableReference>().fVariable->fStorage == Variable::kParameter_Storage &&
-        left.as<VariableReference>().fVariable->fModifiers.fFlags & Modifiers::kOut_Flag) {
+        left.as<VariableReference>().fVariable->storage() == Variable::kParameter_Storage &&
+        left.as<VariableReference>().fVariable->modifiers().fFlags & Modifiers::kOut_Flag) {
         // writing to an out parameter. Since we have to turn those into pointers, we have to
         // dereference it here.
         this->write("*");
@@ -955,7 +955,7 @@
                 const GlobalVarDeclaration& decls = e.as<GlobalVarDeclaration>();
                 const VarDeclaration& var = *decls.fDecl;
                 if (var.fVar->type().typeKind() == Type::TypeKind::kSampler) {
-                    if (var.fVar->fModifiers.fLayout.fBinding < 0) {
+                    if (var.fVar->modifiers().fLayout.fBinding < 0) {
                         fErrors.error(decls.fOffset,
                                         "Metal samplers must have 'layout(binding=...)'");
                         return;
@@ -968,13 +968,13 @@
                     this->write(", texture2d<float> ");
                     this->writeName(var.fVar->name());
                     this->write("[[texture(");
-                    this->write(to_string(var.fVar->fModifiers.fLayout.fBinding));
+                    this->write(to_string(var.fVar->modifiers().fLayout.fBinding));
                     this->write(")]]");
                     this->write(", sampler ");
                     this->writeName(var.fVar->name());
                     this->write(SAMPLER_SUFFIX);
                     this->write("[[sampler(");
-                    this->write(to_string(var.fVar->fModifiers.fLayout.fBinding));
+                    this->write(to_string(var.fVar->modifiers().fLayout.fBinding));
                     this->write(")]]");
                 }
             } else if (e.kind() == ProgramElement::Kind::kInterfaceBlock) {
@@ -987,7 +987,7 @@
                 this->write("& " );
                 this->write(fInterfaceBlockNameMap[&intf]);
                 this->write(" [[buffer(");
-                this->write(to_string(intf.fVariable.fModifiers.fLayout.fBinding));
+                this->write(to_string(intf.fVariable.modifiers().fLayout.fBinding));
                 this->write(")]]");
             }
         }
@@ -1036,7 +1036,7 @@
     for (const auto& param : f.fDeclaration.fParameters) {
         this->write(separator);
         separator = ", ";
-        this->writeModifiers(param->fModifiers, false);
+        this->writeModifiers(param->modifiers(), false);
         std::vector<int> sizes;
         const Type* type = &param->type();
         while (type->typeKind() == Type::TypeKind::kArray) {
@@ -1044,7 +1044,7 @@
             type = &type->componentType();
         }
         this->writeType(*type);
-        if (param->fModifiers.fFlags & Modifiers::kOut_Flag) {
+        if (param->modifiers().fFlags & Modifiers::kOut_Flag) {
             this->write("*");
         }
         this->write(" ");
@@ -1113,7 +1113,7 @@
     if ("sk_PerVertex" == intf.fTypeName) {
         return;
     }
-    this->writeModifiers(intf.fVariable.fModifiers, true);
+    this->writeModifiers(intf.fVariable.modifiers(), true);
     this->write("struct ");
     this->writeLine(intf.fTypeName + " {");
     const Type* structType = &intf.fVariable.type();
@@ -1215,10 +1215,10 @@
 }
 
 void MetalCodeGenerator::writeVarDeclaration(const VarDeclaration& var, bool global) {
-    if (global && !(var.fVar->fModifiers.fFlags & Modifiers::kConst_Flag)) {
+    if (global && !(var.fVar->modifiers().fFlags & Modifiers::kConst_Flag)) {
         return;
     }
-    this->writeModifiers(var.fVar->fModifiers, global);
+    this->writeModifiers(var.fVar->modifiers(), global);
     this->writeType(var.fBaseType);
     this->write(" ");
     this->writeName(var.fVar->name());
@@ -1393,15 +1393,15 @@
         if (e.is<GlobalVarDeclaration>()) {
             const GlobalVarDeclaration& decls = e.as<GlobalVarDeclaration>();
             const Variable& var = *decls.fDecl->fVar;
-            if (var.fModifiers.fFlags & Modifiers::kUniform_Flag &&
+            if (var.modifiers().fFlags & Modifiers::kUniform_Flag &&
                 var.type().typeKind() != Type::TypeKind::kSampler) {
                 if (-1 == fUniformBuffer) {
                     this->write("struct Uniforms {\n");
-                    fUniformBuffer = var.fModifiers.fLayout.fSet;
+                    fUniformBuffer = var.modifiers().fLayout.fSet;
                     if (-1 == fUniformBuffer) {
                         fErrors.error(decls.fOffset, "Metal uniforms must have 'layout(set=...)'");
                     }
-                } else if (var.fModifiers.fLayout.fSet != fUniformBuffer) {
+                } else if (var.modifiers().fLayout.fSet != fUniformBuffer) {
                     if (-1 == fUniformBuffer) {
                         fErrors.error(decls.fOffset, "Metal backend requires all uniforms to have "
                                     "the same 'layout(set=...)'");
@@ -1426,19 +1426,19 @@
         if (e.is<GlobalVarDeclaration>()) {
             const GlobalVarDeclaration& decls = e.as<GlobalVarDeclaration>();
             const Variable& var = *decls.fDecl->fVar;
-            if (var.fModifiers.fFlags & Modifiers::kIn_Flag &&
-                -1 == var.fModifiers.fLayout.fBuiltin) {
+            if (var.modifiers().fFlags & Modifiers::kIn_Flag &&
+                -1 == var.modifiers().fLayout.fBuiltin) {
                 this->write("    ");
                 this->writeType(var.type());
                 this->write(" ");
                 this->writeName(var.name());
-                if (-1 != var.fModifiers.fLayout.fLocation) {
+                if (-1 != var.modifiers().fLayout.fLocation) {
                     if (fProgram.fKind == Program::kVertex_Kind) {
                         this->write("  [[attribute(" +
-                                    to_string(var.fModifiers.fLayout.fLocation) + ")]]");
+                                    to_string(var.modifiers().fLayout.fLocation) + ")]]");
                     } else if (fProgram.fKind == Program::kFragment_Kind) {
                         this->write("  [[user(locn" +
-                                    to_string(var.fModifiers.fLayout.fLocation) + ")]]");
+                                    to_string(var.modifiers().fLayout.fLocation) + ")]]");
                     }
                 }
                 this->write(";\n");
@@ -1459,19 +1459,19 @@
         if (e.is<GlobalVarDeclaration>()) {
             const GlobalVarDeclaration& decls = e.as<GlobalVarDeclaration>();
             const Variable& var = *decls.fDecl->fVar;
-            if (var.fModifiers.fFlags & Modifiers::kOut_Flag &&
-                -1 == var.fModifiers.fLayout.fBuiltin) {
+            if (var.modifiers().fFlags & Modifiers::kOut_Flag &&
+                -1 == var.modifiers().fLayout.fBuiltin) {
                 this->write("    ");
                 this->writeType(var.type());
                 this->write(" ");
                 this->writeName(var.name());
                 if (fProgram.fKind == Program::kVertex_Kind) {
                     this->write("  [[user(locn" +
-                                to_string(var.fModifiers.fLayout.fLocation) + ")]]");
+                                to_string(var.modifiers().fLayout.fLocation) + ")]]");
                 } else if (fProgram.fKind == Program::kFragment_Kind) {
                     this->write(" [[color(" +
-                                to_string(var.fModifiers.fLayout.fLocation) +")");
-                    int colorIndex = var.fModifiers.fLayout.fIndex;
+                                to_string(var.modifiers().fLayout.fLocation) +")");
+                    int colorIndex = var.modifiers().fLayout.fIndex;
                     if (colorIndex) {
                         this->write(", index(" + to_string(colorIndex) + ")");
                     }
@@ -1514,7 +1514,7 @@
         const GlobalVarDeclaration& decls = element.as<GlobalVarDeclaration>();
         const VarDeclaration& decl = *decls.fDecl;
         const Variable& var = *decl.fVar;
-        if ((!var.fModifiers.fFlags && -1 == var.fModifiers.fLayout.fBuiltin) ||
+        if ((!var.modifiers().fFlags && -1 == var.modifiers().fLayout.fBuiltin) ||
             var.type().typeKind() == Type::TypeKind::kSampler) {
             if (var.type().typeKind() == Type::TypeKind::kSampler) {
                 // Samplers are represented as a "texture/sampler" duo in the global struct.
@@ -1638,7 +1638,7 @@
             break;
         case ProgramElement::Kind::kGlobalVar: {
             const VarDeclaration& decl = *e.as<GlobalVarDeclaration>().fDecl;
-            int builtin = decl.fVar->fModifiers.fLayout.fBuiltin;
+            int builtin = decl.fVar->modifiers().fLayout.fBuiltin;
             if (-1 == builtin) {
                 // normal var
                 this->writeVarDeclaration(decl, true);
@@ -1716,15 +1716,16 @@
         }
         case Expression::Kind::kVariableReference: {
             const VariableReference& v = e->as<VariableReference>();
+            const Modifiers& modifiers = v.fVariable->modifiers();
             Requirements result = kNo_Requirements;
-            if (v.fVariable->fModifiers.fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) {
+            if (modifiers.fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) {
                 result = kGlobals_Requirement | kFragCoord_Requirement;
-            } else if (Variable::kGlobal_Storage == v.fVariable->fStorage) {
-                if (v.fVariable->fModifiers.fFlags & Modifiers::kIn_Flag) {
+            } else if (Variable::kGlobal_Storage == v.fVariable->storage()) {
+                if (modifiers.fFlags & Modifiers::kIn_Flag) {
                     result = kInputs_Requirement;
-                } else if (v.fVariable->fModifiers.fFlags & Modifiers::kOut_Flag) {
+                } else if (modifiers.fFlags & Modifiers::kOut_Flag) {
                     result = kOutputs_Requirement;
-                } else if (v.fVariable->fModifiers.fFlags & Modifiers::kUniform_Flag &&
+                } else if (modifiers.fFlags & Modifiers::kUniform_Flag &&
                            v.fVariable->type().typeKind() != Type::TypeKind::kSampler) {
                     result = kUniforms_Requirement;
                 } else {
diff --git a/src/sksl/SkSLModifiersPool.h b/src/sksl/SkSLModifiersPool.h
new file mode 100644
index 0000000..31e9bc4
--- /dev/null
+++ b/src/sksl/SkSLModifiersPool.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2020 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_MODIFIERSPOOL
+#define SKSL_MODIFIERSPOOL
+
+#include <unordered_map>
+
+namespace SkSL {
+
+struct Modifiers;
+
+/**
+ * Stores a pool of Modifiers objects. Modifiers is fairly heavy, so to reduce IRNode's size we only
+ * store a handle to the Modifiers inside of the node and keep the object itself in a ModifiersPool.
+ */
+class ModifiersPool {
+public:
+    class Handle {
+    public:
+        Handle() = default;
+
+        Handle(const ModifiersPool* pool, int index)
+            : fPool(pool)
+            , fIndex(index) {}
+
+        const Modifiers* operator->() const {
+            return &fPool->fModifiers[fIndex];
+        }
+
+        const Modifiers& operator*() const {
+            return fPool->fModifiers[fIndex];
+        }
+
+    private:
+        const ModifiersPool* fPool;
+        int fIndex;
+    };
+
+    bool empty() {
+        return fModifiers.empty();
+    }
+
+    Handle handle(const Modifiers& modifiers) {
+        SkASSERT(fModifiers.size() == fModifiersMap.size());
+        int index;
+        auto found = fModifiersMap.find(modifiers);
+        if (found != fModifiersMap.end()) {
+            index = found->second;
+        } else {
+            index = fModifiers.size();
+            fModifiers.push_back(modifiers);
+            fModifiersMap.insert({modifiers, index});
+        }
+        return Handle(this, index);
+    }
+
+    void finish() {
+        fModifiersMap.clear();
+    }
+
+private:
+    std::vector<Modifiers> fModifiers;
+    std::unordered_map<Modifiers, int> fModifiersMap;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/src/sksl/SkSLPipelineStageCodeGenerator.cpp b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
index 35e2aae..8b03c5b 100644
--- a/src/sksl/SkSLPipelineStageCodeGenerator.cpp
+++ b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
@@ -107,7 +107,7 @@
 }
 
 void PipelineStageCodeGenerator::writeVariableReference(const VariableReference& ref) {
-    switch (ref.fVariable->fModifiers.fLayout.fBuiltin) {
+    switch (ref.fVariable->modifiers().fLayout.fBuiltin) {
         case SK_OUTCOLOR_BUILTIN:
             this->write(Compiler::kFormatArgPlaceholderStr);
             fArgs->fFormatArgs.push_back(Compiler::FormatArg(Compiler::FormatArg::Kind::kOutput));
@@ -130,7 +130,7 @@
                             found = true;
                             break;
                         }
-                        if (var.fModifiers.fFlags & flag) {
+                        if (var.modifiers().fFlags & flag) {
                             ++index;
                         }
                     }
@@ -139,12 +139,12 @@
                 return index;
             };
 
-            if (ref.fVariable->fModifiers.fFlags & Modifiers::kUniform_Flag) {
+            if (ref.fVariable->modifiers().fFlags & Modifiers::kUniform_Flag) {
                 this->write(Compiler::kFormatArgPlaceholderStr);
                 fArgs->fFormatArgs.push_back(
                         Compiler::FormatArg(Compiler::FormatArg::Kind::kUniform,
                                             varIndexByFlag(Modifiers::kUniform_Flag)));
-            } else if (ref.fVariable->fModifiers.fFlags & Modifiers::kVarying_Flag) {
+            } else if (ref.fVariable->modifiers().fFlags & Modifiers::kVarying_Flag) {
                 this->write("_vtx_attr_");
                 this->write(to_string(varIndexByFlag(Modifiers::kVarying_Flag)));
             } else {
@@ -214,9 +214,9 @@
     }
     if (p.is<GlobalVarDeclaration>()) {
         const Variable& var = *p.as<GlobalVarDeclaration>().fDecl->fVar;
-        if (var.fModifiers.fFlags &
+        if (var.modifiers().fFlags &
                     (Modifiers::kIn_Flag | Modifiers::kUniform_Flag | Modifiers::kVarying_Flag) ||
-            -1 != var.fModifiers.fLayout.fBuiltin) {
+            var.modifiers().fLayout.fBuiltin == -1) {
             return;
         }
     }
diff --git a/src/sksl/SkSLRehydrator.cpp b/src/sksl/SkSLRehydrator.cpp
index c778bf4..3e9d7bf 100644
--- a/src/sksl/SkSLRehydrator.cpp
+++ b/src/sksl/SkSLRehydrator.cpp
@@ -126,7 +126,7 @@
     }
 }
 
-const Symbol* Rehydrator::symbol() {
+Symbol* Rehydrator::symbol() {
     int kind = this->readU8();
     switch (kind) {
         case kArrayType_Command: {
@@ -139,7 +139,7 @@
             } else {
                 name += "[" + to_string(count) + "]";
             }
-            const Type* result = fSymbolTable->takeOwnershipOfSymbol(
+            Type* result = fSymbolTable->takeOwnershipOfSymbol(
                     std::make_unique<Type>(name, Type::TypeKind::kArray, *componentType, count));
             this->addSymbol(id, result);
             return result;
@@ -147,7 +147,7 @@
         case kEnumType_Command: {
             uint16_t id = this->readU16();
             StringFragment name = this->readString();
-            const Type* result = fSymbolTable->takeOwnershipOfSymbol(
+            Type* result = fSymbolTable->takeOwnershipOfSymbol(
                     std::make_unique<Type>(name, Type::TypeKind::kEnum));
             this->addSymbol(id, result);
             return result;
@@ -157,13 +157,13 @@
             Modifiers modifiers = this->modifiers();
             StringFragment name = this->readString();
             int parameterCount = this->readU8();
-            std::vector<const Variable*> parameters;
+            std::vector<Variable*> parameters;
             parameters.reserve(parameterCount);
             for (int i = 0; i < parameterCount; ++i) {
                 parameters.push_back(this->symbolRef<Variable>(Symbol::Kind::kVariable));
             }
             const Type* returnType = this->type();
-            const FunctionDeclaration* result =
+            FunctionDeclaration* result =
                     fSymbolTable->takeOwnershipOfSymbol(std::make_unique<FunctionDeclaration>(
                             /*offset=*/-1, modifiers, name, std::move(parameters), *returnType,
                             /*builtin=*/true));
@@ -173,14 +173,14 @@
         case kField_Command: {
             const Variable* owner = this->symbolRef<Variable>(Symbol::Kind::kVariable);
             uint8_t index = this->readU8();
-            const Field* result = fSymbolTable->takeOwnershipOfSymbol(
+            Field* result = fSymbolTable->takeOwnershipOfSymbol(
                     std::make_unique<Field>(/*offset=*/-1, owner, index));
             return result;
         }
         case kNullableType_Command: {
             uint16_t id = this->readU16();
             const Type* base = this->type();
-            const Type* result = fSymbolTable->takeOwnershipOfSymbol(
+            Type* result = fSymbolTable->takeOwnershipOfSymbol(
                     std::make_unique<Type>(base->name() + "?", Type::TypeKind::kNullable, *base));
             this->addSymbol(id, result);
             return result;
@@ -197,7 +197,7 @@
                 const Type* type = this->type();
                 fields.emplace_back(m, fieldName, type);
             }
-            const Type* result = fSymbolTable->takeOwnershipOfSymbol(
+            Type* result = fSymbolTable->takeOwnershipOfSymbol(
                     std::make_unique<Type>(/*offset=*/-1, name, std::move(fields)));
             this->addSymbol(id, result);
             return result;
@@ -210,8 +210,8 @@
         case kSymbolAlias_Command: {
             uint16_t id = this->readU16();
             StringFragment name = this->readString();
-            const Symbol* origSymbol = this->symbol();
-            const SymbolAlias* symbolAlias = fSymbolTable->takeOwnershipOfSymbol(
+            Symbol* origSymbol = this->symbol();
+            SymbolAlias* symbolAlias = fSymbolTable->takeOwnershipOfSymbol(
                     std::make_unique<SymbolAlias>(/*offset=*/-1, name, origSymbol));
             this->addSymbol(id, symbolAlias);
             return symbolAlias;
@@ -219,7 +219,7 @@
         case kSystemType_Command: {
             uint16_t id = this->readU16();
             StringFragment name = this->readString();
-            const Symbol* result = (*fSymbolTable)[name];
+            Symbol* result = (*fSymbolTable)[name];
             SkASSERT(result && result->kind() == Symbol::Kind::kType);
             this->addSymbol(id, result);
             return result;
@@ -234,18 +234,18 @@
                 SkASSERT(f && f->kind() == Symbol::Kind::kFunctionDeclaration);
                 functions.push_back((const FunctionDeclaration*) f);
             }
-            const UnresolvedFunction* result = fSymbolTable->takeOwnershipOfSymbol(
+            UnresolvedFunction* result = fSymbolTable->takeOwnershipOfSymbol(
                     std::make_unique<UnresolvedFunction>(std::move(functions)));
             this->addSymbol(id, result);
             return result;
         }
         case kVariable_Command: {
             uint16_t id = this->readU16();
-            Modifiers m = this->modifiers();
+            ModifiersPool::Handle m = fModifiers.handle(this->modifiers());
             StringFragment name = this->readString();
             const Type* type = this->type();
             Variable::Storage storage = (Variable::Storage) this->readU8();
-            const Variable* result = fSymbolTable->takeOwnershipOfSymbol(std::make_unique<Variable>(
+            Variable* result = fSymbolTable->takeOwnershipOfSymbol(std::make_unique<Variable>(
                     /*offset=*/-1, m, name, type, /*builtin=*/true, storage));
             this->addSymbol(id, result);
             return result;
@@ -285,9 +285,8 @@
                 SkASSERT(s->kind() == Symbol::Kind::kVariable);
                 Variable& v = (Variable&) *s;
                 int value = this->readS32();
-                v.fInitialValue = symbols->takeOwnershipOfIRNode(
-                        std::make_unique<IntLiteral>(fContext, /*offset=*/-1, value));
-                v.fWriteCount = 1;
+                v.setInitialValue(symbols->takeOwnershipOfIRNode(
+                        std::make_unique<IntLiteral>(fContext, /*offset=*/-1, value)));
             }
             return std::unique_ptr<ProgramElement>(new Enum(-1, typeName, std::move(symbols)));
         }
@@ -429,9 +428,7 @@
             }
             std::unique_ptr<Expression> value = this->expression();
             if (value) {
-                var->fInitialValue = value.get();
-                SkASSERT(var->fWriteCount == 0);
-                ++var->fWriteCount;
+                var->setInitialValue(value.get());
             }
             return std::unique_ptr<Statement>(
                     new VarDeclaration(var, baseType, std::move(sizes), std::move(value)));
@@ -567,7 +564,7 @@
     std::shared_ptr<SymbolTable> result = inherit ? std::make_shared<SymbolTable>(fSymbolTable)
                                                   : std::make_shared<SymbolTable>(fErrors);
     fSymbolTable = result;
-    std::vector<const Symbol*> ownedSymbols;
+    std::vector<Symbol*> ownedSymbols;
     ownedSymbols.reserve(ownedCount);
     for (int i = 0; i < ownedCount; ++i) {
         ownedSymbols.push_back(this->symbol());
diff --git a/src/sksl/SkSLRehydrator.h b/src/sksl/SkSLRehydrator.h
index 18bdda4..f254f05 100644
--- a/src/sksl/SkSLRehydrator.h
+++ b/src/sksl/SkSLRehydrator.h
@@ -20,6 +20,7 @@
 class Context;
 class ErrorReporter;
 struct Expression;
+class IRGenerator;
 struct ProgramElement;
 struct Statement;
 class SymbolTable;
@@ -144,9 +145,11 @@
     };
 
     // src must remain in memory as long as the objects created from it do
-    Rehydrator(Context* context, std::shared_ptr<SymbolTable> symbolTable,
-               ErrorReporter* errorReporter, const uint8_t* src, size_t length)
+    Rehydrator(const Context* context, ModifiersPool* modifiers,
+               std::shared_ptr<SymbolTable> symbolTable, ErrorReporter* errorReporter,
+               const uint8_t* src, size_t length)
         : fContext(*context)
+        , fModifiers(*modifiers)
         , fErrors(errorReporter)
         , fSymbolTable(std::move(symbolTable))
         , fStart(src)
@@ -200,7 +203,7 @@
         return StringFragment(chars, length);
     }
 
-    void addSymbol(int id, const Symbol* symbol) {
+    void addSymbol(int id, Symbol* symbol) {
         while ((size_t) id >= fSymbols.size()) {
             fSymbols.push_back(nullptr);
         }
@@ -218,7 +221,7 @@
 
     Modifiers modifiers();
 
-    const Symbol* symbol();
+    Symbol* symbol();
 
     std::unique_ptr<ProgramElement> element();
 
@@ -228,10 +231,11 @@
 
     const Type* type();
 
-    Context& fContext;
+    const Context& fContext;
+    ModifiersPool& fModifiers;
     ErrorReporter* fErrors;
     std::shared_ptr<SymbolTable> fSymbolTable;
-    std::vector<const Symbol*> fSymbols;
+    std::vector<Symbol*> fSymbols;
 
     const uint8_t* fStart;
     const uint8_t* fIP;
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.cpp b/src/sksl/SkSLSPIRVCodeGenerator.cpp
index 05cbea6..5e7dbab 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/SkSLSPIRVCodeGenerator.cpp
@@ -179,7 +179,7 @@
 }
 
 static bool is_out(const Variable& var) {
-    return (var.fModifiers.fFlags & Modifiers::kOut_Flag) != 0;
+    return (var.modifiers().fFlags & Modifiers::kOut_Flag) != 0;
 }
 
 void SPIRVCodeGenerator::writeOpCode(SpvOp_ opCode, int length, OutputStream& out) {
@@ -717,7 +717,7 @@
             SpvId result = this->nextId();
             std::vector<SpvId> argumentIds;
             for (size_t i = 0; i < arguments.size(); i++) {
-                if (function.fParameters[i]->fModifiers.fFlags & Modifiers::kOut_Flag) {
+                if (function.fParameters[i]->modifiers().fFlags & Modifiers::kOut_Flag) {
                     argumentIds.push_back(this->getLValue(*arguments[i], out)->getPointer());
                 } else {
                     argumentIds.push_back(this->writeExpression(*arguments[i], out));
@@ -737,7 +737,7 @@
             SpvId result = this->nextId();
             std::vector<SpvId> argumentIds;
             for (size_t i = 0; i < arguments.size(); i++) {
-                if (function.fParameters[i]->fModifiers.fFlags & Modifiers::kOut_Flag) {
+                if (function.fParameters[i]->modifiers().fFlags & Modifiers::kOut_Flag) {
                     argumentIds.push_back(this->getLValue(*arguments[i], out)->getPointer());
                 } else {
                     argumentIds.push_back(this->writeExpression(*arguments[i], out));
@@ -1564,10 +1564,10 @@
     switch (expr.kind()) {
         case Expression::Kind::kVariableReference: {
             const Variable& var = *expr.as<VariableReference>().fVariable;
-            if (var.fStorage != Variable::kGlobal_Storage) {
+            if (var.storage() != Variable::kGlobal_Storage) {
                 return SpvStorageClassFunction;
             }
-            SpvStorageClass_ result = get_storage_class(var.fModifiers);
+            SpvStorageClass_ result = get_storage_class(var.modifiers());
             if (result == SpvStorageClassFunction) {
                 result = SpvStorageClassPrivate;
             }
@@ -1727,7 +1727,7 @@
         case Expression::Kind::kVariableReference: {
             SpvId typeId;
             const Variable& var = *expr.as<VariableReference>().fVariable;
-            if (var.fModifiers.fLayout.fBuiltin == SK_IN_BUILTIN) {
+            if (var.modifiers().fLayout.fBuiltin == SK_IN_BUILTIN) {
                 typeId = this->getType(Type("sk_in", Type::TypeKind::kArray,
                                             var.type().componentType(), fSkInCount));
             } else {
@@ -1838,7 +1838,7 @@
     SpvId var = entry->second;
     this->writeInstruction(SpvOpLoad, this->getType(ref.fVariable->type()), result, var, out);
     this->writePrecisionModifier(ref.fVariable->type(), result);
-    if (ref.fVariable->fModifiers.fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN &&
+    if (ref.fVariable->modifiers().fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN &&
         (fProgram.fSettings.fFlipY || fProgram.fSettings.fInverseW)) {
         // The x component never changes, so just grab it
         SpvId xId = this->nextId();
@@ -1875,9 +1875,10 @@
                 Layout layout(0, -1, -1, binding, -1, set, -1, -1, Layout::Format::kUnspecified,
                                 Layout::kUnspecified_Primitive, -1, -1, "", "", Layout::kNo_Key,
                                 Layout::CType::kDefault);
+                Modifiers modifiers(layout, Modifiers::kUniform_Flag);
                 const Variable* intfVar = fSynthetics.takeOwnershipOfSymbol(
                         std::make_unique<Variable>(/*offset=*/-1,
-                                                   Modifiers(layout, Modifiers::kUniform_Flag),
+                                                   fModifiers.handle(modifiers),
                                                    name,
                                                    &intfStruct,
                                                    /*builtin=*/false,
@@ -1946,7 +1947,7 @@
 
         return adjusted;
     }
-    if (ref.fVariable->fModifiers.fLayout.fBuiltin == SK_CLOCKWISE_BUILTIN &&
+    if (ref.fVariable->modifiers().fLayout.fBuiltin == SK_CLOCKWISE_BUILTIN &&
         !fProgram.fSettings.fFlipY) {
         // FrontFacing in Vulkan is defined in terms of a top-down render target. In skia, we use
         // the default convention of "counter-clockwise face is front".
@@ -2692,8 +2693,8 @@
 }
 
 SpvId SPIRVCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf, bool appendRTHeight) {
-    bool isBuffer = (0 != (intf.fVariable.fModifiers.fFlags & Modifiers::kBuffer_Flag));
-    bool pushConstant = (0 != (intf.fVariable.fModifiers.fLayout.fFlags &
+    bool isBuffer = (0 != (intf.fVariable.modifiers().fFlags & Modifiers::kBuffer_Flag));
+    bool pushConstant = (0 != (intf.fVariable.modifiers().fLayout.fFlags &
                                Layout::kPushConstant_Flag));
     MemoryLayout memoryLayout = (pushConstant || isBuffer) ?
                                 MemoryLayout(MemoryLayout::k430_Standard) :
@@ -2710,7 +2711,8 @@
         type = new Type(type->fOffset, type->name(), fields);
     }
     SpvId typeId;
-    if (intf.fVariable.fModifiers.fLayout.fBuiltin == SK_IN_BUILTIN) {
+    Modifiers intfModifiers = intf.fVariable.modifiers();
+    if (intfModifiers.fLayout.fBuiltin == SK_IN_BUILTIN) {
         for (const auto& e : fProgram) {
             if (e.kind() == ProgramElement::Kind::kModifiers) {
                 const Modifiers& m = ((ModifiersDeclaration&) e).fModifiers;
@@ -2724,17 +2726,17 @@
     } else {
         typeId = this->getType(*type, memoryLayout);
     }
-    if (intf.fVariable.fModifiers.fFlags & Modifiers::kBuffer_Flag) {
+    if (intfModifiers.fFlags & Modifiers::kBuffer_Flag) {
         this->writeInstruction(SpvOpDecorate, typeId, SpvDecorationBufferBlock, fDecorationBuffer);
-    } else if (intf.fVariable.fModifiers.fLayout.fBuiltin == -1) {
+    } else if (intfModifiers.fLayout.fBuiltin == -1) {
         this->writeInstruction(SpvOpDecorate, typeId, SpvDecorationBlock, fDecorationBuffer);
     }
-    SpvStorageClass_ storageClass = get_storage_class(intf.fVariable.fModifiers);
+    SpvStorageClass_ storageClass = get_storage_class(intfModifiers);
     SpvId ptrType = this->nextId();
     this->writeInstruction(SpvOpTypePointer, ptrType, storageClass, typeId, fConstantBuffer);
     this->writeInstruction(SpvOpVariable, ptrType, result, storageClass, fConstantBuffer);
-    Layout layout = intf.fVariable.fModifiers.fLayout;
-    if (intf.fVariable.fModifiers.fFlags & Modifiers::kUniform_Flag && layout.fSet == -1) {
+    Layout layout = intfModifiers.fLayout;
+    if (intfModifiers.fFlags & Modifiers::kUniform_Flag && layout.fSet == -1) {
         layout.fSet = 0;
     }
     this->writeLayout(layout, result);
@@ -2756,19 +2758,19 @@
 }
 
 bool is_dead(const Variable& var) {
-    if (var.fReadCount || var.fWriteCount) {
+    if (var.readCount() || var.writeCount()) {
         return false;
     }
     // not entirely sure what the rules are for when it's safe to elide interface variables, but it
     // causes various problems to elide some of them even when dead. But it also causes problems
     // *not* to elide sk_SampleMask when it's not being used.
-    if (!(var.fModifiers.fFlags & (Modifiers::kIn_Flag |
-                                   Modifiers::kOut_Flag |
-                                   Modifiers::kUniform_Flag |
-                                   Modifiers::kBuffer_Flag))) {
+    if (!(var.modifiers().fFlags & (Modifiers::kIn_Flag |
+                                    Modifiers::kOut_Flag |
+                                    Modifiers::kUniform_Flag |
+                                    Modifiers::kBuffer_Flag))) {
         return true;
     }
-    return var.fModifiers.fLayout.fBuiltin == SK_SAMPLEMASK_BUILTIN;
+    return var.modifiers().fLayout.fBuiltin == SK_SAMPLEMASK_BUILTIN;
 }
 
 #define BUILTIN_IGNORE 9999
@@ -2777,15 +2779,15 @@
     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.
-    SkASSERT(!(var->fModifiers.fFlags & (Modifiers::kReadOnly_Flag |
-                                         Modifiers::kWriteOnly_Flag |
-                                         Modifiers::kCoherent_Flag |
-                                         Modifiers::kVolatile_Flag |
-                                         Modifiers::kRestrict_Flag)));
-    if (var->fModifiers.fLayout.fBuiltin == BUILTIN_IGNORE) {
+    SkASSERT(!(var->modifiers().fFlags & (Modifiers::kReadOnly_Flag |
+                                          Modifiers::kWriteOnly_Flag |
+                                          Modifiers::kCoherent_Flag |
+                                          Modifiers::kVolatile_Flag |
+                                          Modifiers::kRestrict_Flag)));
+    if (var->modifiers().fLayout.fBuiltin == BUILTIN_IGNORE) {
         return;
     }
-    if (var->fModifiers.fLayout.fBuiltin == SK_FRAGCOLOR_BUILTIN &&
+    if (var->modifiers().fLayout.fBuiltin == SK_FRAGCOLOR_BUILTIN &&
         kind != Program::kFragment_Kind) {
         SkASSERT(!fProgram.fSettings.fFragColorIsInOut);
         return;
@@ -2795,11 +2797,11 @@
     }
     const Type& type = var->type();
     SpvStorageClass_ storageClass;
-    if (var->fModifiers.fFlags & Modifiers::kIn_Flag) {
+    if (var->modifiers().fFlags & Modifiers::kIn_Flag) {
         storageClass = SpvStorageClassInput;
-    } else if (var->fModifiers.fFlags & Modifiers::kOut_Flag) {
+    } else if (var->modifiers().fFlags & Modifiers::kOut_Flag) {
         storageClass = SpvStorageClassOutput;
-    } else if (var->fModifiers.fFlags & Modifiers::kUniform_Flag) {
+    } else if (var->modifiers().fFlags & Modifiers::kUniform_Flag) {
         if (type.typeKind() == Type::TypeKind::kSampler ||
             type.typeKind() == Type::TypeKind::kSeparateSampler ||
             type.typeKind() == Type::TypeKind::kTexture) {
@@ -2813,7 +2815,7 @@
     SpvId id = this->nextId();
     fVariableMap[var] = id;
     SpvId typeId;
-    if (var->fModifiers.fLayout.fBuiltin == SK_IN_BUILTIN) {
+    if (var->modifiers().fLayout.fBuiltin == SK_IN_BUILTIN) {
         typeId = this->getPointerType(
                 Type("sk_in", Type::TypeKind::kArray, type.componentType(), fSkInCount),
                 storageClass);
@@ -2830,11 +2832,11 @@
         this->writeInstruction(SpvOpStore, id, value, fGlobalInitializersBuffer);
         fCurrentBlock = 0;
     }
-    this->writeLayout(var->fModifiers.fLayout, id);
-    if (var->fModifiers.fFlags & Modifiers::kFlat_Flag) {
+    this->writeLayout(var->modifiers().fLayout, id);
+    if (var->modifiers().fFlags & Modifiers::kFlat_Flag) {
         this->writeInstruction(SpvOpDecorate, id, SpvDecorationFlat, fDecorationBuffer);
     }
-    if (var->fModifiers.fFlags & Modifiers::kNoPerspective_Flag) {
+    if (var->modifiers().fFlags & Modifiers::kNoPerspective_Flag) {
         this->writeInstruction(SpvOpDecorate, id, SpvDecorationNoPerspective,
                                 fDecorationBuffer);
     }
@@ -2844,11 +2846,11 @@
     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.
-    SkASSERT(!(var->fModifiers.fFlags & (Modifiers::kReadOnly_Flag |
-                                        Modifiers::kWriteOnly_Flag |
-                                        Modifiers::kCoherent_Flag |
-                                        Modifiers::kVolatile_Flag |
-                                        Modifiers::kRestrict_Flag)));
+    SkASSERT(!(var->modifiers().fFlags & (Modifiers::kReadOnly_Flag |
+                                          Modifiers::kWriteOnly_Flag |
+                                          Modifiers::kCoherent_Flag |
+                                          Modifiers::kVolatile_Flag |
+                                          Modifiers::kRestrict_Flag)));
     SpvId id = this->nextId();
     fVariableMap[var] = id;
     SpvId type = this->getPointerType(var->type(), SpvStorageClassFunction);
@@ -3192,14 +3194,15 @@
     for (const auto& e : program) {
         if (e.kind() == ProgramElement::Kind::kInterfaceBlock) {
             InterfaceBlock& intf = (InterfaceBlock&) e;
-            if (SK_IN_BUILTIN == intf.fVariable.fModifiers.fLayout.fBuiltin) {
+            const Modifiers& modifiers = intf.fVariable.modifiers();
+            if (SK_IN_BUILTIN == modifiers.fLayout.fBuiltin) {
                 SkASSERT(skInSize != -1);
                 intf.fSizes.emplace_back(new IntLiteral(fContext, -1, skInSize));
             }
             SpvId id = this->writeInterfaceBlock(intf);
-            if (((intf.fVariable.fModifiers.fFlags & Modifiers::kIn_Flag) ||
-                (intf.fVariable.fModifiers.fFlags & Modifiers::kOut_Flag)) &&
-                intf.fVariable.fModifiers.fLayout.fBuiltin == -1 &&
+            if (((modifiers.fFlags & Modifiers::kIn_Flag) ||
+                (modifiers.fFlags & Modifiers::kOut_Flag)) &&
+                modifiers.fLayout.fBuiltin == -1 &&
                 !is_dead(intf.fVariable)) {
                 interfaceVars.insert(id);
             }
@@ -3227,9 +3230,9 @@
     }
     for (auto entry : fVariableMap) {
         const Variable* var = entry.first;
-        if (var->fStorage == Variable::kGlobal_Storage &&
-            ((var->fModifiers.fFlags & Modifiers::kIn_Flag) ||
-             (var->fModifiers.fFlags & Modifiers::kOut_Flag)) && !is_dead(*var)) {
+        if (var->storage() == Variable::kGlobal_Storage &&
+            ((var->modifiers().fFlags & Modifiers::kIn_Flag) ||
+             (var->modifiers().fFlags & Modifiers::kOut_Flag)) && !is_dead(*var)) {
             interfaceVars.insert(entry.second);
         }
     }
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.h b/src/sksl/SkSLSPIRVCodeGenerator.h
index 2bc8b32..21f9f4e 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.h
+++ b/src/sksl/SkSLSPIRVCodeGenerator.h
@@ -104,10 +104,11 @@
         virtual void store(SpvId value, OutputStream& out) = 0;
     };
 
-    SPIRVCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors,
-                       OutputStream* out)
+    SPIRVCodeGenerator(const Context* context, ModifiersPool* modifiers,
+                       const Program* program, ErrorReporter* errors, OutputStream* out)
     : INHERITED(program, errors, out)
     , fContext(*context)
+    , fModifiers(*modifiers)
     , fDefaultLayout(MemoryLayout::k140_Standard)
     , fCapabilities(0)
     , fIdCount(1)
@@ -366,6 +367,7 @@
     void writeGeometryShaderExecutionMode(SpvId entryPoint, OutputStream& out);
 
     const Context& fContext;
+    ModifiersPool& fModifiers;
     const MemoryLayout fDefaultLayout;
 
     uint64_t fCapabilities;
diff --git a/src/sksl/SkSLSectionAndParameterHelper.h b/src/sksl/SkSLSectionAndParameterHelper.h
index e35f27d..6dc7cf4 100644
--- a/src/sksl/SkSLSectionAndParameterHelper.h
+++ b/src/sksl/SkSLSectionAndParameterHelper.h
@@ -63,8 +63,8 @@
     }
 
     static bool IsParameter(const Variable& var) {
-        return (var.fModifiers.fFlags & Modifiers::kIn_Flag) &&
-               -1 == var.fModifiers.fLayout.fBuiltin;
+        return (var.modifiers().fFlags & Modifiers::kIn_Flag) &&
+               -1 == var.modifiers().fLayout.fBuiltin;
     }
 
     static bool IsSupportedSection(const char* name) {
diff --git a/src/sksl/ir/SkSLEnum.h b/src/sksl/ir/SkSLEnum.h
index 6859f48..c5bbadd 100644
--- a/src/sksl/ir/SkSLEnum.h
+++ b/src/sksl/ir/SkSLEnum.h
@@ -55,7 +55,7 @@
         std::sort(sortedSymbols.begin(), sortedSymbols.end(),
                   [](const Symbol* a, const Symbol* b) { return a->name() < b->name(); });
         for (const auto& s : sortedSymbols) {
-            const Expression& initialValue = *s->as<Variable>().fInitialValue;
+            const Expression& initialValue = *s->as<Variable>().initialValue();
             result += separator + "    " + s->name() + " = " +
                       to_string(initialValue.as<IntLiteral>().value());
             separator = ",\n";
diff --git a/src/sksl/ir/SkSLExpression.h b/src/sksl/ir/SkSLExpression.h
index 7646268..746f4ce 100644
--- a/src/sksl/ir/SkSLExpression.h
+++ b/src/sksl/ir/SkSLExpression.h
@@ -18,7 +18,7 @@
 
 struct Expression;
 class IRGenerator;
-struct Variable;
+class Variable;
 
 using DefinitionMap = TinyUnorderedMap<const Variable*, std::unique_ptr<Expression>*>;
 
diff --git a/src/sksl/ir/SkSLFunctionDeclaration.h b/src/sksl/ir/SkSLFunctionDeclaration.h
index f4bfbd0..ffbd3d1 100644
--- a/src/sksl/ir/SkSLFunctionDeclaration.h
+++ b/src/sksl/ir/SkSLFunctionDeclaration.h
@@ -28,7 +28,7 @@
     static constexpr Kind kSymbolKind = Kind::kFunctionDeclaration;
 
     FunctionDeclaration(int offset, Modifiers modifiers, StringFragment name,
-                        std::vector<const Variable*> parameters, const Type& returnType,
+                        std::vector<Variable*> parameters, const Type& returnType,
                         bool builtin)
     : INHERITED(offset, kSymbolKind, std::move(name))
     , fDefinition(nullptr)
@@ -118,7 +118,7 @@
     mutable FunctionDefinition* fDefinition;
     bool fBuiltin;
     Modifiers fModifiers;
-    const std::vector<const Variable*> fParameters;
+    const std::vector<Variable*> fParameters;
     const Type& fReturnType;
     mutable std::atomic<int> fCallCount = 0;
 
diff --git a/src/sksl/ir/SkSLIRNode.cpp b/src/sksl/ir/SkSLIRNode.cpp
index 3361ebb..7fbcd4e 100644
--- a/src/sksl/ir/SkSLIRNode.cpp
+++ b/src/sksl/ir/SkSLIRNode.cpp
@@ -83,15 +83,10 @@
 , fKind(kind)
 , fData(data) {}
 
-IRNode::IRNode(const IRNode& other)
-    : fOffset(other.fOffset)
-    , fKind(other.fKind)
-    , fData(other.fData) {
-    // For now, we can't use a default copy constructor because of the std::unique_ptr children.
-    // Since we never copy nodes containing children, it's easiest just to assert we don't have any
-    // than bother with cloning them.
-    SkASSERT(other.fExpressionChildren.empty());
-}
+IRNode::IRNode(int offset, int kind, const VariableData& data)
+: fOffset(offset)
+, fKind(kind)
+, fData(data) {}
 
 IRNode::~IRNode() {}
 
diff --git a/src/sksl/ir/SkSLIRNode.h b/src/sksl/ir/SkSLIRNode.h
index 012d3dc..18c9b2f 100644
--- a/src/sksl/ir/SkSLIRNode.h
+++ b/src/sksl/ir/SkSLIRNode.h
@@ -10,6 +10,7 @@
 
 #include "src/sksl/SkSLASTNode.h"
 #include "src/sksl/SkSLLexer.h"
+#include "src/sksl/SkSLModifiersPool.h"
 #include "src/sksl/SkSLString.h"
 
 #include <algorithm>
@@ -24,7 +25,7 @@
 class Symbol;
 class SymbolTable;
 class Type;
-struct Variable;
+class Variable;
 
 /**
  * Represents a node in the intermediate representation (IR) tree. The IR is a fully-resolved
@@ -72,6 +73,8 @@
                 return *this->typeData();
             case NodeData::Kind::kTypeToken:
                 return *this->typeTokenData().fType;
+            case NodeData::Kind::kVariable:
+                return *this->variableData().fType;
             default:
                 SkUNREACHABLE;
         }
@@ -135,7 +138,7 @@
 
     struct SymbolAliasData {
         StringFragment fName;
-        const Symbol* fOrigSymbol;
+        Symbol* fOrigSymbol;
     };
 
     struct TypeTokenData {
@@ -143,6 +146,21 @@
         Token::Kind fToken;
     };
 
+    struct VariableData {
+        StringFragment fName;
+        const Type* fType;
+        const Expression* fInitialValue = nullptr;
+        ModifiersPool::Handle fModifiersHandle;
+        // Tracks how many sites read from the variable. If this is zero for a non-out variable (or
+        // becomes zero during optimization), the variable is dead and may be eliminated.
+        mutable int16_t fReadCount;
+        // Tracks how many sites write to the variable. If this is zero, the variable is dead and
+        // may be eliminated.
+        mutable int16_t fWriteCount;
+        /*Variable::Storage*/int8_t fStorage;
+        bool fBuiltin;
+    };
+
     struct NodeData {
         enum class Kind {
             kBlock,
@@ -159,6 +177,7 @@
             kSymbolAlias,
             kType,
             kTypeToken,
+            kVariable,
         } fKind = Kind::kType;
         // it doesn't really matter what kind we default to, as long as it's a POD type
 
@@ -177,6 +196,7 @@
             SymbolAliasData fSymbolAlias;
             const Type* fType;
             TypeTokenData fTypeToken;
+            VariableData fVariable;
 
             Contents() {}
 
@@ -253,6 +273,11 @@
             *(new(&fContents) TypeTokenData) = data;
         }
 
+        NodeData(const VariableData& data)
+            : fKind(Kind::kVariable) {
+            *(new(&fContents) VariableData) = data;
+        }
+
         NodeData(const NodeData& other) {
             *this = other;
         }
@@ -303,6 +328,9 @@
                 case Kind::kTypeToken:
                     *(new(&fContents) TypeTokenData) = other.fContents.fTypeToken;
                     break;
+                case Kind::kVariable:
+                    *(new(&fContents) VariableData) = other.fContents.fVariable;
+                    break;
             }
             return *this;
         }
@@ -355,6 +383,9 @@
                 case Kind::kTypeToken:
                     fContents.fTypeToken.~TypeTokenData();
                     break;
+                case Kind::kVariable:
+                    fContents.fVariable.~VariableData();
+                    break;
             }
         }
     };
@@ -388,7 +419,7 @@
 
     IRNode(int offset, int kind, const TypeTokenData& data);
 
-    IRNode(const IRNode& other);
+    IRNode(int offset, int kind, const VariableData& data);
 
     Expression& expressionChild(int index) const {
         SkASSERT(index >= 0 && index < (int) fExpressionChildren.size());
@@ -509,6 +540,16 @@
         return fData.fContents.fTypeToken;
     }
 
+    VariableData& variableData() {
+        SkASSERT(fData.fKind == NodeData::Kind::kVariable);
+        return fData.fContents.fVariable;
+    }
+
+    const VariableData& variableData() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kVariable);
+        return fData.fContents.fVariable;
+    }
+
     int fKind;
 
     NodeData fData;
diff --git a/src/sksl/ir/SkSLInterfaceBlock.h b/src/sksl/ir/SkSLInterfaceBlock.h
index 2bc2f79..cee0a22 100644
--- a/src/sksl/ir/SkSLInterfaceBlock.h
+++ b/src/sksl/ir/SkSLInterfaceBlock.h
@@ -49,7 +49,7 @@
     }
 
     String description() const override {
-        String result = fVariable.fModifiers.description() + fTypeName + " {\n";
+        String result = fVariable.modifiers().description() + fTypeName + " {\n";
         const Type* structType = &fVariable.type();
         while (structType->typeKind() == Type::TypeKind::kArray) {
             structType = &structType->componentType();
diff --git a/src/sksl/ir/SkSLModifiers.h b/src/sksl/ir/SkSLModifiers.h
index f830b0f..7030377 100644
--- a/src/sksl/ir/SkSLModifiers.h
+++ b/src/sksl/ir/SkSLModifiers.h
@@ -10,6 +10,8 @@
 
 #include "src/sksl/ir/SkSLLayout.h"
 
+#include <vector>
+
 namespace SkSL {
 
 /**
@@ -116,6 +118,17 @@
     int fFlags;
 };
 
-}  // namespace SkSL
+} // namespace SkSL
+
+namespace std {
+
+template <>
+struct hash<SkSL::Modifiers> {
+    size_t operator()(const SkSL::Modifiers& key) const {
+        return key.fFlags ^ (key.fLayout.fFlags << 8) ^ ((unsigned) key.fLayout.fBuiltin << 16);
+    }
+};
+
+} // namespace std
 
 #endif
diff --git a/src/sksl/ir/SkSLProgram.h b/src/sksl/ir/SkSLProgram.h
index bf22189..0a5d667 100644
--- a/src/sksl/ir/SkSLProgram.h
+++ b/src/sksl/ir/SkSLProgram.h
@@ -250,6 +250,7 @@
             std::shared_ptr<Context> context,
             std::vector<std::unique_ptr<ProgramElement>>* inheritedElements,
             std::vector<std::unique_ptr<ProgramElement>> elements,
+            std::unique_ptr<ModifiersPool> modifiers,
             std::shared_ptr<SymbolTable> symbols,
             Inputs inputs)
     : fKind(kind)
@@ -259,7 +260,8 @@
     , fSymbols(symbols)
     , fInputs(inputs)
     , fInheritedElements(inheritedElements)
-    , fElements(std::move(elements)) {}
+    , fElements(std::move(elements))
+    , fModifiers(std::move(modifiers)) {}
 
     iterator begin() {
         if (fInheritedElements) {
@@ -293,6 +295,10 @@
         return const_iterator(fElements.end(), fElements.end(), fElements.end(), fElements.end());
     }
 
+    void finish() {
+        fModifiers->finish();
+    }
+
     Kind fKind;
     std::unique_ptr<String> fSource;
     Settings fSettings;
@@ -305,6 +311,7 @@
 private:
     std::vector<std::unique_ptr<ProgramElement>>* fInheritedElements;
     std::vector<std::unique_ptr<ProgramElement>> fElements;
+    std::unique_ptr<ModifiersPool> fModifiers;
 
     friend class Compiler;
 };
diff --git a/src/sksl/ir/SkSLSymbol.h b/src/sksl/ir/SkSLSymbol.h
index 3d18c1a..76e7c58 100644
--- a/src/sksl/ir/SkSLSymbol.h
+++ b/src/sksl/ir/SkSLSymbol.h
@@ -42,7 +42,9 @@
     Symbol(int offset, const SymbolAliasData& data)
     : INHERITED(offset, (int) Kind::kSymbolAlias, data) {}
 
-    Symbol(const Symbol&) = default;
+    Symbol(int offset, const VariableData& data)
+    : INHERITED(offset, (int) Kind::kVariable, data) {}
+
     Symbol& operator=(const Symbol&) = default;
 
     ~Symbol() override {}
diff --git a/src/sksl/ir/SkSLSymbolAlias.h b/src/sksl/ir/SkSLSymbolAlias.h
index 109824f..a84f25f 100644
--- a/src/sksl/ir/SkSLSymbolAlias.h
+++ b/src/sksl/ir/SkSLSymbolAlias.h
@@ -19,14 +19,14 @@
 public:
     static constexpr Kind kSymbolKind = Kind::kSymbolAlias;
 
-    SymbolAlias(int offset, StringFragment name, const Symbol* origSymbol)
+    SymbolAlias(int offset, StringFragment name, Symbol* origSymbol)
     : INHERITED(offset, SymbolAliasData{name, origSymbol}) {}
 
     StringFragment name() const override {
         return this->symbolAliasData().fName;
     }
 
-    const Symbol* origSymbol() const {
+    Symbol* origSymbol() const {
         return this->symbolAliasData().fOrigSymbol;
     }
 
diff --git a/src/sksl/ir/SkSLSymbolTable.cpp b/src/sksl/ir/SkSLSymbolTable.cpp
index 26a7cb6..5e9b30e 100644
--- a/src/sksl/ir/SkSLSymbolTable.cpp
+++ b/src/sksl/ir/SkSLSymbolTable.cpp
@@ -23,8 +23,8 @@
     }
 }
 
-const Symbol* SymbolTable::operator[](StringFragment name) {
-    const auto& entry = fSymbols.find(name);
+Symbol* SymbolTable::operator[](StringFragment name) {
+    auto entry = fSymbols.find(name);
     if (entry == fSymbols.end()) {
         if (fParent) {
             return (*fParent)[name];
@@ -59,7 +59,7 @@
             }
         }
     }
-    const Symbol* symbol = entry->second;
+    Symbol* symbol = entry->second;
     while (symbol && symbol->is<SymbolAlias>()) {
         symbol = symbol->as<SymbolAlias>().origSymbol();
     }
@@ -72,14 +72,14 @@
     return result;
 }
 
-void SymbolTable::addAlias(StringFragment name, const Symbol* symbol) {
+void SymbolTable::addAlias(StringFragment name, Symbol* symbol) {
     this->add(name, std::make_unique<SymbolAlias>(symbol->fOffset, name, symbol));
 }
 
-void SymbolTable::addWithoutOwnership(StringFragment name, const Symbol* symbol) {
+void SymbolTable::addWithoutOwnership(StringFragment name, Symbol* symbol) {
     SkASSERT(symbol->name() == name);
 
-    const Symbol*& refInSymbolTable = fSymbols[name];
+    Symbol*& refInSymbolTable = fSymbols[name];
     if (refInSymbolTable == nullptr) {
         refInSymbolTable = symbol;
         return;
@@ -106,11 +106,11 @@
     }
 }
 
-std::unordered_map<StringFragment, const Symbol*>::iterator SymbolTable::begin() {
+std::unordered_map<StringFragment, Symbol*>::iterator SymbolTable::begin() {
     return fSymbols.begin();
 }
 
-std::unordered_map<StringFragment, const Symbol*>::iterator SymbolTable::end() {
+std::unordered_map<StringFragment, Symbol*>::iterator SymbolTable::end() {
     return fSymbols.end();
 }
 
diff --git a/src/sksl/ir/SkSLSymbolTable.h b/src/sksl/ir/SkSLSymbolTable.h
index b10341c..1c36de0 100644
--- a/src/sksl/ir/SkSLSymbolTable.h
+++ b/src/sksl/ir/SkSLSymbolTable.h
@@ -31,38 +31,38 @@
     : fParent(parent)
     , fErrorReporter(parent->fErrorReporter) {}
 
-    const Symbol* operator[](StringFragment name);
+    Symbol* operator[](StringFragment name);
 
-    void addAlias(StringFragment name, const Symbol* symbol);
-    void addWithoutOwnership(StringFragment name, const Symbol* symbol);
+    void addAlias(StringFragment name, Symbol* symbol);
+    void addWithoutOwnership(StringFragment name, Symbol* symbol);
 
     template <typename T>
     const T* add(StringFragment name, std::unique_ptr<T> symbol) {
-        const T* ptr = symbol.get();
+        T* ptr = symbol.get();
         this->addWithoutOwnership(name, ptr);
         this->takeOwnershipOfSymbol(std::move(symbol));
         return ptr;
     }
 
     template <typename T>
-    const T* takeOwnershipOfSymbol(std::unique_ptr<T> symbol) {
-        const T* ptr = symbol.get();
+    T* takeOwnershipOfSymbol(std::unique_ptr<T> symbol) {
+        T* ptr = symbol.get();
         fOwnedSymbols.push_back(std::move(symbol));
         return ptr;
     }
 
     template <typename T>
-    const T* takeOwnershipOfIRNode(std::unique_ptr<T> node) {
-        const T* ptr = node.get();
+    T* takeOwnershipOfIRNode(std::unique_ptr<T> node) {
+        T* ptr = node.get();
         fOwnedNodes.push_back(std::move(node));
         return ptr;
     }
 
     const String* takeOwnershipOfString(std::unique_ptr<String> n);
 
-    std::unordered_map<StringFragment, const Symbol*>::iterator begin();
+    std::unordered_map<StringFragment, Symbol*>::iterator begin();
 
-    std::unordered_map<StringFragment, const Symbol*>::iterator end();
+    std::unordered_map<StringFragment, Symbol*>::iterator end();
 
     std::shared_ptr<SymbolTable> fParent;
 
@@ -75,7 +75,7 @@
 
     std::vector<std::unique_ptr<String>> fOwnedStrings;
 
-    std::unordered_map<StringFragment, const Symbol*> fSymbols;
+    std::unordered_map<StringFragment, Symbol*> fSymbols;
 
     ErrorReporter& fErrorReporter;
 
diff --git a/src/sksl/ir/SkSLVarDeclarations.h b/src/sksl/ir/SkSLVarDeclarations.h
index 21883f5..5ddb4b6 100644
--- a/src/sksl/ir/SkSLVarDeclarations.h
+++ b/src/sksl/ir/SkSLVarDeclarations.h
@@ -47,8 +47,8 @@
     }
 
     String description() const override {
-        String result =
-                fVar->fModifiers.description() + fBaseType.description() + " " + fVar->name();
+        String result = fVar->modifiers().description() + fBaseType.description() + " " +
+                        fVar->name();
         for (const auto& size : fSizes) {
             if (size) {
                 result += "[" + size->description() + "]";
diff --git a/src/sksl/ir/SkSLVariable.h b/src/sksl/ir/SkSLVariable.h
index e1a21c8..e5a39dd 100644
--- a/src/sksl/ir/SkSLVariable.h
+++ b/src/sksl/ir/SkSLVariable.h
@@ -12,6 +12,7 @@
 #include "src/sksl/ir/SkSLModifiers.h"
 #include "src/sksl/ir/SkSLSymbol.h"
 #include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
 
 namespace SkSL {
 
@@ -22,7 +23,8 @@
  * variable itself (the storage location), which is shared between all VariableReferences which
  * read or write that storage location.
  */
-struct Variable : public Symbol {
+class Variable : public Symbol {
+public:
     static constexpr Kind kSymbolKind = Kind::kVariable;
 
     enum Storage {
@@ -32,53 +34,102 @@
         kParameter_Storage
     };
 
-    Variable(int offset, Modifiers modifiers, StringFragment name, const Type* type,
+    Variable(int offset, ModifiersPool::Handle modifiers, StringFragment name, const Type* type,
              bool builtin, Storage storage, Expression* initialValue = nullptr)
-    : INHERITED(offset, kSymbolKind, name, type)
-    , fModifiers(modifiers)
-    , fStorage(storage)
-    , fInitialValue(initialValue)
-    , fBuiltin(builtin)
-    , fReadCount(0)
-    , fWriteCount(initialValue ? 1 : 0) {}
+    : INHERITED(offset, VariableData{name, type, initialValue, modifiers, /*readCount=*/0,
+                                     /*writeCount=*/(int16_t) (initialValue ? 1 : 0),
+                                     (int8_t) storage, builtin}) {}
 
     ~Variable() override {
         // can't destroy a variable while there are remaining references to it
-        if (fInitialValue) {
-            --fWriteCount;
+        if (this->initialValue()) {
+            --this->variableData().fWriteCount;
         }
-        SkASSERT(!fReadCount && !fWriteCount);
+        SkASSERT(!this->variableData().fReadCount && !this->variableData().fWriteCount);
+    }
+
+    const Modifiers& modifiers() const {
+        return *this->variableData().fModifiersHandle;
+    }
+
+    const ModifiersPool::Handle& modifiersHandle() const {
+        return this->variableData().fModifiersHandle;
+    }
+
+    void setModifiersHandle(const ModifiersPool::Handle& handle) {
+        this->variableData().fModifiersHandle = handle;
+    }
+
+    bool isBuiltin() const {
+        return this->variableData().fBuiltin;
+    }
+
+    Storage storage() const {
+        return (Storage) this->variableData().fStorage;
+    }
+
+    const Expression* initialValue() const {
+        return this->variableData().fInitialValue;
+    }
+
+    void setInitialValue(const Expression* initialValue) {
+        SkASSERT(!this->initialValue());
+        SkASSERT(this->variableData().fWriteCount == 0);
+        this->variableData().fInitialValue = initialValue;
+        ++this->variableData().fWriteCount;
+    }
+
+    int readCount() const {
+        return this->variableData().fReadCount;
+    }
+
+    int writeCount() const {
+        return this->variableData().fWriteCount;
+    }
+
+    StringFragment name() const override {
+        return this->variableData().fName;
     }
 
     String description() const override {
-        return fModifiers.description() + this->type().name() + " " + this->name();
+        return this->modifiers().description() + this->type().name() + " " + this->name();
     }
 
     bool dead() const {
-        if ((fStorage != kLocal_Storage && fReadCount) ||
-            (fModifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag |
+        const VariableData& data = this->variableData();
+        const Modifiers& modifiers = this->modifiers();
+        if ((data.fStorage != kLocal_Storage && this->variableData().fReadCount) ||
+            (modifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag |
                                  Modifiers::kUniform_Flag | Modifiers::kVarying_Flag))) {
             return false;
         }
-        return !fWriteCount ||
-               (!fReadCount && !(fModifiers.fFlags & (Modifiers::kPLS_Flag |
-                                                      Modifiers::kPLSOut_Flag)));
+        return !data.fWriteCount ||
+               (!data.fReadCount && !(modifiers.fFlags & (Modifiers::kPLS_Flag |
+                                                          Modifiers::kPLSOut_Flag)));
     }
 
-    mutable Modifiers fModifiers;
-    const Storage fStorage;
+private:
+    void referenceCreated(VariableReference::RefKind refKind) const {
+        if (refKind != VariableReference::kRead_RefKind) {
+            ++this->variableData().fWriteCount;
+        }
+        if (refKind != VariableReference::kWrite_RefKind) {
+            ++this->variableData().fReadCount;
+        }
+    }
 
-    const Expression* fInitialValue = nullptr;
-    bool fBuiltin;
-
-    // Tracks how many sites read from the variable. If this is zero for a non-out variable (or
-    // becomes zero during optimization), the variable is dead and may be eliminated.
-    mutable int fReadCount;
-    // Tracks how many sites write to the variable. If this is zero, the variable is dead and may be
-    // eliminated.
-    mutable int fWriteCount;
+    void referenceDestroyed(VariableReference::RefKind refKind) const {
+        if (refKind != VariableReference::kRead_RefKind) {
+            --this->variableData().fWriteCount;
+        }
+        if (refKind != VariableReference::kWrite_RefKind) {
+            --this->variableData().fReadCount;
+        }
+    }
 
     using INHERITED = Symbol;
+
+    friend struct VariableReference;
 };
 
 } // namespace SkSL
diff --git a/src/sksl/ir/SkSLVariableReference.cpp b/src/sksl/ir/SkSLVariableReference.cpp
index 1387287..376036a 100644
--- a/src/sksl/ir/SkSLVariableReference.cpp
+++ b/src/sksl/ir/SkSLVariableReference.cpp
@@ -19,41 +19,41 @@
         , fVariable(variable)
         , fRefKind(refKind) {
     SkASSERT(fVariable);
-    this->incrementRefs();
+    fVariable->referenceCreated(fRefKind);
 }
 
 VariableReference::~VariableReference() {
-    this->decrementRefs();
+    fVariable->referenceDestroyed(fRefKind);
 }
 
-void VariableReference::incrementRefs() const {
-    if (fRefKind != kRead_RefKind) {
-        fVariable->fWriteCount++;
-    }
-    if (fRefKind != kWrite_RefKind) {
-        fVariable->fReadCount++;
+bool VariableReference::hasProperty(Property property) const {
+    switch (property) {
+        case Property::kSideEffects:      return false;
+        case Property::kContainsRTAdjust: return fVariable->name() == "sk_RTAdjust";
+        default:
+            SkASSERT(false);
+            return false;
     }
 }
 
-void VariableReference::decrementRefs() const {
-    if (fRefKind != kRead_RefKind) {
-        fVariable->fWriteCount--;
-    }
-    if (fRefKind != kWrite_RefKind) {
-        fVariable->fReadCount--;
-    }
+bool VariableReference::isConstantOrUniform() const {
+    return (fVariable->modifiers().fFlags & Modifiers::kUniform_Flag) != 0;
+}
+
+String VariableReference::description() const {
+    return fVariable->name();
 }
 
 void VariableReference::setRefKind(RefKind refKind) {
-    this->decrementRefs();
+    fVariable->referenceDestroyed(fRefKind);
     fRefKind = refKind;
-    this->incrementRefs();
+    fVariable->referenceCreated(fRefKind);
 }
 
 void VariableReference::setVariable(const Variable* variable) {
-    this->decrementRefs();
+    fVariable->referenceDestroyed(fRefKind);
     fVariable = variable;
-    this->incrementRefs();
+    fVariable->referenceCreated(fRefKind);
 }
 
 std::unique_ptr<Expression> VariableReference::constantPropagate(const IRGenerator& irGenerator,
@@ -61,10 +61,11 @@
     if (fRefKind != kRead_RefKind) {
         return nullptr;
     }
-    if ((fVariable->fModifiers.fFlags & Modifiers::kConst_Flag) && fVariable->fInitialValue &&
-        fVariable->fInitialValue->isCompileTimeConstant() &&
+    const Expression* initialValue = fVariable->initialValue();
+    if ((fVariable->modifiers().fFlags & Modifiers::kConst_Flag) && initialValue &&
+        initialValue->isCompileTimeConstant() &&
         this->type().typeKind() != Type::TypeKind::kArray) {
-        return fVariable->fInitialValue->clone();
+        return initialValue->clone();
     }
     auto exprIter = definitions.find(fVariable);
     if (exprIter != definitions.end() && exprIter->second &&
diff --git a/src/sksl/ir/SkSLVariableReference.h b/src/sksl/ir/SkSLVariableReference.h
index 6f38b9c..7b98ed5 100644
--- a/src/sksl/ir/SkSLVariableReference.h
+++ b/src/sksl/ir/SkSLVariableReference.h
@@ -9,11 +9,11 @@
 #define SKSL_VARIABLEREFERENCE
 
 #include "src/sksl/ir/SkSLExpression.h"
-#include "src/sksl/ir/SkSLVariable.h"
 
 namespace SkSL {
 
 class IRGenerator;
+class Variable;
 
 /**
  * A reference to a variable, through which it can be read or written. In the statement:
@@ -48,27 +48,15 @@
     void setRefKind(RefKind refKind);
     void setVariable(const Variable* variable);
 
-    bool hasProperty(Property property) const override {
-        switch (property) {
-            case Property::kSideEffects:      return false;
-            case Property::kContainsRTAdjust: return fVariable->name() == "sk_RTAdjust";
-            default:
-                SkASSERT(false);
-                return false;
-        }
-    }
+    bool hasProperty(Property property) const override;
 
-    bool isConstantOrUniform() const override {
-        return (fVariable->fModifiers.fFlags & Modifiers::kUniform_Flag) != 0;
-    }
+    bool isConstantOrUniform() const override;
 
     std::unique_ptr<Expression> clone() const override {
         return std::unique_ptr<Expression>(new VariableReference(fOffset, fVariable, fRefKind));
     }
 
-    String description() const override {
-        return fVariable->name();
-    }
+    String description() const override;
 
     std::unique_ptr<Expression> constantPropagate(const IRGenerator& irGenerator,
                                                   const DefinitionMap& definitions) override;
@@ -77,9 +65,6 @@
     RefKind fRefKind;
 
 private:
-    void incrementRefs() const;
-    void decrementRefs() const;
-
     using INHERITED = Expression;
 };