Moved Var/VarDecl conversion out of IRGenerator

Change-Id: Ie2c39ede6ee10445091f2f71fd32a30a2d6a5e7c
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/457121
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 2871d58..7be396e 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -65,142 +65,6 @@
 IRGenerator::IRGenerator(const Context* context)
         : fContext(*context) {}
 
-void IRGenerator::checkVarDeclaration(int line, const Modifiers& modifiers, const Type* baseType,
-                                      Variable::Storage storage) {
-    if (this->strictES2Mode() && baseType->isArray()) {
-        this->errorReporter().error(line, "array size must appear after variable name");
-    }
-
-    if (baseType->componentType().isOpaque() && storage != Variable::Storage::kGlobal) {
-        this->errorReporter().error(
-                line,
-                "variables of type '" + baseType->displayName() + "' must be global");
-    }
-    if ((modifiers.fFlags & Modifiers::kIn_Flag) && baseType->isMatrix()) {
-        this->errorReporter().error(line, "'in' variables may not have matrix type");
-    }
-    if ((modifiers.fFlags & Modifiers::kIn_Flag) && (modifiers.fFlags & Modifiers::kUniform_Flag)) {
-        this->errorReporter().error(line, "'in uniform' variables not permitted");
-    }
-    if (this->isRuntimeEffect()) {
-        if (modifiers.fFlags & Modifiers::kIn_Flag) {
-            this->errorReporter().error(line, "'in' variables not permitted in runtime effects");
-        }
-    }
-    if (baseType->isEffectChild() && !(modifiers.fFlags & Modifiers::kUniform_Flag)) {
-        this->errorReporter().error(
-                line, "variables of type '" + baseType->displayName() + "' must be uniform");
-    }
-    if (modifiers.fLayout.fFlags & Layout::kSRGBUnpremul_Flag) {
-        if (!this->isRuntimeEffect()) {
-            this->errorReporter().error(line,
-                                        "'srgb_unpremul' is only permitted in runtime effects");
-        }
-        if (!(modifiers.fFlags & Modifiers::kUniform_Flag)) {
-            this->errorReporter().error(line,
-                                        "'srgb_unpremul' is only permitted on 'uniform' variables");
-        }
-        auto validColorXformType = [](const Type& t) {
-            return t.isVector() && t.componentType().isFloat() &&
-                   (t.columns() == 3 || t.columns() == 4);
-        };
-        if (!validColorXformType(*baseType) && !(baseType->isArray() &&
-                                                 validColorXformType(baseType->componentType()))) {
-            this->errorReporter().error(line,
-                                        "'srgb_unpremul' is only permitted on half3, half4, "
-                                        "float3, or float4 variables");
-        }
-    }
-    int permitted = Modifiers::kConst_Flag | Modifiers::kHighp_Flag | Modifiers::kMediump_Flag |
-                    Modifiers::kLowp_Flag;
-    if (storage == Variable::Storage::kGlobal) {
-        permitted |= Modifiers::kIn_Flag | Modifiers::kOut_Flag | Modifiers::kUniform_Flag |
-                     Modifiers::kFlat_Flag | Modifiers::kNoPerspective_Flag;
-    }
-    // TODO(skbug.com/11301): Migrate above checks into building a mask of permitted layout flags
-    CheckModifiers(fContext, line, modifiers, permitted, /*permittedLayoutFlags=*/~0);
-}
-
-std::unique_ptr<Variable> IRGenerator::convertVar(int line, const Modifiers& modifiers,
-                                                  const Type* baseType, skstd::string_view name,
-                                                  bool isArray,
-                                                  std::unique_ptr<Expression> arraySize,
-                                                  Variable::Storage storage) {
-    if (modifiers.fLayout.fLocation == 0 && modifiers.fLayout.fIndex == 0 &&
-        (modifiers.fFlags & Modifiers::kOut_Flag) &&
-        this->programKind() == ProgramKind::kFragment && name != Compiler::FRAGCOLOR_NAME) {
-        this->errorReporter().error(line,
-                                    "out location=0, index=0 is reserved for sk_FragColor");
-    }
-    const Type* type = baseType;
-    int arraySizeValue = 0;
-    if (isArray) {
-        SkASSERT(arraySize);
-        arraySizeValue = type->convertArraySize(fContext, std::move(arraySize));
-        if (!arraySizeValue) {
-            return {};
-        }
-        type = fSymbolTable->addArrayDimension(type, arraySizeValue);
-    }
-    return std::make_unique<Variable>(line, this->modifiersPool().add(modifiers), name,
-                                      type, fContext.fConfig->fIsBuiltinCode, storage);
-}
-
-std::unique_ptr<Statement> IRGenerator::convertVarDeclaration(std::unique_ptr<Variable> var,
-                                                              std::unique_ptr<Expression> value,
-                                                              bool addToSymbolTable) {
-    std::unique_ptr<Statement> varDecl = VarDeclaration::Convert(fContext, var.get(),
-                                                                 std::move(value));
-    if (!varDecl) {
-        return nullptr;
-    }
-
-    // Detect the declaration of magical variables.
-    if ((var->storage() == Variable::Storage::kGlobal) && var->name() == Compiler::FRAGCOLOR_NAME) {
-        // Silently ignore duplicate definitions of `sk_FragColor`.
-        const Symbol* symbol = (*fSymbolTable)[var->name()];
-        if (symbol) {
-            return nullptr;
-        }
-    } else if ((var->storage() == Variable::Storage::kGlobal ||
-                var->storage() == Variable::Storage::kInterfaceBlock) &&
-               var->name() == Compiler::RTADJUST_NAME) {
-        // `sk_RTAdjust` is special, and makes the IR generator emit position-fixup expressions.
-        if (fRTAdjust) {
-            this->errorReporter().error(var->fLine, "duplicate definition of 'sk_RTAdjust'");
-            return nullptr;
-        }
-        if (var->type() != *fContext.fTypes.fFloat4) {
-            this->errorReporter().error(var->fLine, "sk_RTAdjust must have type 'float4'");
-            return nullptr;
-        }
-        fRTAdjust = var.get();
-    }
-
-    if (addToSymbolTable) {
-        fSymbolTable->add(std::move(var));
-    } else {
-        fSymbolTable->takeOwnershipOfSymbol(std::move(var));
-    }
-    return varDecl;
-}
-
-std::unique_ptr<Statement> IRGenerator::convertVarDeclaration(int line,
-                                                              const Modifiers& modifiers,
-                                                              const Type* baseType,
-                                                              skstd::string_view name,
-                                                              bool isArray,
-                                                              std::unique_ptr<Expression> arraySize,
-                                                              std::unique_ptr<Expression> value,
-                                                              Variable::Storage storage) {
-    std::unique_ptr<Variable> var = this->convertVar(line, modifiers, baseType, name, isArray,
-                                                     std::move(arraySize), storage);
-    if (!var) {
-        return nullptr;
-    }
-    return this->convertVarDeclaration(std::move(var), std::move(value));
-}
-
 void IRGenerator::appendRTAdjustFixupToVertexMain(const FunctionDeclaration& decl, Block* body) {
     using namespace SkSL::dsl;
     using SkSL::dsl::Swizzle;  // disambiguate from SkSL::Swizzle
diff --git a/src/sksl/SkSLIRGenerator.h b/src/sksl/SkSLIRGenerator.h
index 992016e..73e1ec9 100644
--- a/src/sksl/SkSLIRGenerator.h
+++ b/src/sksl/SkSLIRGenerator.h
@@ -94,6 +94,7 @@
     int getRTAdjustFieldIndex() { return fRTAdjustFieldIndex; }
 
     const Context& fContext;
+    const Variable* fRTAdjust = nullptr;
 
 private:
     void start(const ParsedModule& base,
@@ -102,23 +103,6 @@
 
     IRGenerator::IRBundle finish();
 
-    void checkVarDeclaration(int line,
-                             const Modifiers& modifiers,
-                             const Type* baseType,
-                             Variable::Storage storage);
-    std::unique_ptr<Variable> convertVar(int line, const Modifiers& modifiers,
-                                         const Type* baseType, skstd::string_view name,
-                                         bool isArray, std::unique_ptr<Expression> arraySize,
-                                         Variable::Storage storage);
-    std::unique_ptr<Statement> convertVarDeclaration(std::unique_ptr<Variable> var,
-                                                     std::unique_ptr<Expression> value,
-                                                     bool addToSymbolTable = true);
-    std::unique_ptr<Statement> convertVarDeclaration(int line, const Modifiers& modifiers,
-                                                     const Type* baseType, skstd::string_view name,
-                                                     bool isArray,
-                                                     std::unique_ptr<Expression> arraySize,
-                                                     std::unique_ptr<Expression> value,
-                                                     Variable::Storage storage);
     void scanInterfaceBlock(SkSL::InterfaceBlock& intf);
     /** Appends sk_Position fixup to the bottom of main() if this is a vertex program. */
     void appendRTAdjustFixupToVertexMain(const FunctionDeclaration& decl, Block* body);
@@ -147,7 +131,6 @@
     std::unordered_set<const Type*> fDefinedStructs;
     std::vector<std::unique_ptr<ProgramElement>>* fProgramElements = nullptr;
     std::vector<const ProgramElement*>*           fSharedElements = nullptr;
-    const Variable* fRTAdjust = nullptr;
     const Variable* fRTAdjustInterfaceBlock = nullptr;
     int fRTAdjustFieldIndex;
 
diff --git a/src/sksl/dsl/DSLCore.cpp b/src/sksl/dsl/DSLCore.cpp
index 1e73307..e8750af 100644
--- a/src/sksl/dsl/DSLCore.cpp
+++ b/src/sksl/dsl/DSLCore.cpp
@@ -225,7 +225,7 @@
             if (baseType->isArray()) {
                 baseType = &baseType->componentType();
             }
-            ThreadContext::IRGenerator().checkVarDeclaration(pos.line(),
+            SkSL::VarDeclaration::ErrorCheck(ThreadContext::Context(), pos.line(),
                     field.fModifiers.fModifiers, baseType, Variable::Storage::kInterfaceBlock);
             GetErrorReporter().reportPendingErrors(field.fPosition);
             skslFields.push_back(SkSL::Type::Field(field.fModifiers.fModifiers, field.fName,
diff --git a/src/sksl/dsl/priv/DSLWriter.cpp b/src/sksl/dsl/priv/DSLWriter.cpp
index faa6f1f..d180154 100644
--- a/src/sksl/dsl/priv/DSLWriter.cpp
+++ b/src/sksl/dsl/priv/DSLWriter.cpp
@@ -47,10 +47,8 @@
             if (baseType->isArray()) {
                 baseType = &baseType->componentType();
             }
-            ThreadContext::IRGenerator().checkVarDeclaration(var.fPosition.line(),
-                    var.fModifiers.fModifiers, baseType, var.storage());
         }
-        std::unique_ptr<SkSL::Variable> skslvar = ThreadContext::IRGenerator().convertVar(
+        std::unique_ptr<SkSL::Variable> skslvar = SkSL::Variable::Convert(ThreadContext::Context(),
                 var.fPosition.line(), var.fModifiers.fModifiers, &var.fType.skslType(), var.fName,
                 /*isArray=*/false, /*arraySize=*/nullptr, var.storage());
         SkSL::Variable* varPtr = skslvar.get();
@@ -62,9 +60,8 @@
             // FunctionDeclaration is created which makes this the wrong spot for them, and outside
             // of DSLParser we don't even need DSL variables to show up in the symbol table in the
             // first place.
-            var.fDeclaration = ThreadContext::IRGenerator().convertVarDeclaration(
-                    std::move(skslvar), var.fInitialValue.releaseIfPossible(),
-                    /*addToSymbolTable=*/false);
+            var.fDeclaration = VarDeclaration::Convert(ThreadContext::Context(), std::move(skslvar),
+                    var.fInitialValue.releaseIfPossible(), /*addToSymbolTable=*/false);
             if (var.fDeclaration) {
                 var.fVar = varPtr;
                 var.fInitialized = true;
@@ -79,9 +76,9 @@
     // This should only be called on undeclared parameter variables, but we allow the creation to go
     // ahead regardless so we don't have to worry about null pointers potentially sneaking in and
     // breaking things. DSLFunction is responsible for reporting errors for invalid parameters.
-    return ThreadContext::IRGenerator().convertVar(var.fPosition.line(), var.fModifiers.fModifiers,
-            &var.fType.skslType(), var.fName, /*isArray=*/false, /*arraySize=*/nullptr,
-            var.storage());
+    return SkSL::Variable::Convert(ThreadContext::Context(), var.fPosition.line(),
+            var.fModifiers.fModifiers, &var.fType.skslType(), var.fName, /*isArray=*/false,
+            /*arraySize=*/nullptr, var.storage());
 }
 
 std::unique_ptr<SkSL::Statement> DSLWriter::Declaration(DSLVarBase& var) {
diff --git a/src/sksl/ir/SkSLVarDeclarations.cpp b/src/sksl/ir/SkSLVarDeclarations.cpp
index 537001d..d63397a 100644
--- a/src/sksl/ir/SkSLVarDeclarations.cpp
+++ b/src/sksl/ir/SkSLVarDeclarations.cpp
@@ -9,8 +9,11 @@
 
 #include "include/sksl/SkSLErrorReporter.h"
 #include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLCompiler.h"
 #include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLIRGenerator.h"
 #include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLThreadContext.h"
 
 namespace SkSL {
 
@@ -34,74 +37,169 @@
     return result;
 }
 
-std::unique_ptr<Statement> VarDeclaration::Convert(const Context& context,
-                                                   Variable* var,
-                                                   std::unique_ptr<Expression> value) {
-    if (value) {
-        if (var->type().isOpaque()) {
-            context.fErrors->error(value->fLine, "opaque type '" + var->type().name() +
-                                                   "' cannot use initializer expressions");
-            return nullptr;
-        }
-        if (var->modifiers().fFlags & Modifiers::kIn_Flag) {
-            context.fErrors->error(value->fLine,
-                                   "'in' variables cannot use initializer expressions");
-            return nullptr;
-        }
-        if (var->modifiers().fFlags & Modifiers::kUniform_Flag) {
-            context.fErrors->error(value->fLine,
-                                   "'uniform' variables cannot use initializer expressions");
-            return nullptr;
-        }
-        if (var->storage() == Variable::Storage::kInterfaceBlock) {
-            context.fErrors->error(value->fLine,
-                                   "initializers are not permitted on interface block fields");
-            return nullptr;
-        }
-        value = var->type().coerceExpression(std::move(value), context);
-        if (!value) {
-            return nullptr;
+void VarDeclaration::ErrorCheck(const Context& context, int line, const Modifiers& modifiers,
+        const Type* baseType, Variable::Storage storage) {
+    if (context.fConfig->strictES2Mode() && baseType->isArray()) {
+        context.fErrors->error(line, "array size must appear after variable name");
+    }
+
+    if (baseType->componentType().isOpaque() && storage != Variable::Storage::kGlobal) {
+        context.fErrors->error(line,
+                "variables of type '" + baseType->displayName() + "' must be global");
+    }
+    if ((modifiers.fFlags & Modifiers::kIn_Flag) && baseType->isMatrix()) {
+        context.fErrors->error(line, "'in' variables may not have matrix type");
+    }
+    if ((modifiers.fFlags & Modifiers::kIn_Flag) && (modifiers.fFlags & Modifiers::kUniform_Flag)) {
+        context.fErrors->error(line, "'in uniform' variables not permitted");
+    }
+    if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
+        if (modifiers.fFlags & Modifiers::kIn_Flag) {
+            context.fErrors->error(line, "'in' variables not permitted in runtime effects");
         }
     }
-    if (var->modifiers().fFlags & Modifiers::kConst_Flag) {
+    if (baseType->isEffectChild() && !(modifiers.fFlags & Modifiers::kUniform_Flag)) {
+        context.fErrors->error(line,
+                "variables of type '" + baseType->displayName() + "' must be uniform");
+    }
+    if (modifiers.fLayout.fFlags & Layout::kSRGBUnpremul_Flag) {
+        if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
+            context.fErrors->error(line, "'srgb_unpremul' is only permitted in runtime effects");
+        }
+        if (!(modifiers.fFlags & Modifiers::kUniform_Flag)) {
+            context.fErrors->error(line,
+                    "'srgb_unpremul' is only permitted on 'uniform' variables");
+        }
+        auto validColorXformType = [](const Type& t) {
+            return t.isVector() && t.componentType().isFloat() &&
+                   (t.columns() == 3 || t.columns() == 4);
+        };
+        if (!validColorXformType(*baseType) && !(baseType->isArray() &&
+                                                 validColorXformType(baseType->componentType()))) {
+            context.fErrors->error(line, "'srgb_unpremul' is only permitted on half3, half4, "
+                    "float3, or float4 variables");
+        }
+    }
+    int permitted = Modifiers::kConst_Flag | Modifiers::kHighp_Flag | Modifiers::kMediump_Flag |
+                    Modifiers::kLowp_Flag;
+    if (storage == Variable::Storage::kGlobal) {
+        permitted |= Modifiers::kIn_Flag | Modifiers::kOut_Flag | Modifiers::kUniform_Flag |
+                     Modifiers::kFlat_Flag | Modifiers::kNoPerspective_Flag;
+    }
+    // TODO(skbug.com/11301): Migrate above checks into building a mask of permitted layout flags
+    IRGenerator::CheckModifiers(context, line, modifiers, permitted, /*permittedLayoutFlags=*/~0);
+}
+
+bool VarDeclaration::ErrorCheckAndCoerce(const Context& context, const Variable& var,
+        std::unique_ptr<Expression>& value) {
+    const Type* baseType = &var.type();
+    if (baseType->isArray()) {
+        baseType = &baseType->componentType();
+    }
+    ErrorCheck(context, var.fLine, var.modifiers(), baseType, var.storage());
+    if (value) {
+        if (var.type().isOpaque()) {
+            context.fErrors->error(value->fLine,
+                    "opaque type '" + var.type().name() + "' cannot use initializer expressions");
+            return false;
+        }
+        if (var.modifiers().fFlags & Modifiers::kIn_Flag) {
+            context.fErrors->error(value->fLine,
+                    "'in' variables cannot use initializer expressions");
+            return false;
+        }
+        if (var.modifiers().fFlags & Modifiers::kUniform_Flag) {
+            context.fErrors->error(value->fLine,
+                    "'uniform' variables cannot use initializer expressions");
+            return false;
+        }
+        if (var.storage() == Variable::Storage::kInterfaceBlock) {
+            context.fErrors->error(value->fLine,
+                    "initializers are not permitted on interface block fields");
+            return false;
+        }
+        value = var.type().coerceExpression(std::move(value), context);
         if (!value) {
-            context.fErrors->error(var->fLine, "'const' variables must be initialized");
-            return nullptr;
+            return false;
+        }
+    }
+    if (var.modifiers().fFlags & Modifiers::kConst_Flag) {
+        if (!value) {
+            context.fErrors->error(var.fLine, "'const' variables must be initialized");
+            return false;
         }
         if (!Analysis::IsConstantExpression(*value)) {
             context.fErrors->error(value->fLine,
-                                   "'const' variable initializer must be a constant expression");
-            return nullptr;
+                    "'const' variable initializer must be a constant expression");
+            return false;
         }
     }
-    if (var->storage() == Variable::Storage::kInterfaceBlock) {
-        if (var->type().isOpaque()) {
-            context.fErrors->error(var->fLine, "opaque type '" + var->type().name() +
-                                                 "' is not permitted in an interface block");
-            return nullptr;
+    if (var.storage() == Variable::Storage::kInterfaceBlock) {
+        if (var.type().isOpaque()) {
+            context.fErrors->error(var.fLine, "opaque type '" + var.type().name() +
+                    "' is not permitted in an interface block");
+            return false;
         }
     }
-    if (var->storage() == Variable::Storage::kGlobal) {
+    if (var.storage() == Variable::Storage::kGlobal) {
         if (value && !Analysis::IsConstantExpression(*value)) {
             context.fErrors->error(value->fLine,
-                                   "global variable initializer must be a constant expression");
-            return nullptr;
+                    "global variable initializer must be a constant expression");
+            return false;
         }
     }
+    return true;
+}
+
+std::unique_ptr<Statement> VarDeclaration::Convert(const Context& context,
+        std::unique_ptr<Variable> var, std::unique_ptr<Expression> value, bool addToSymbolTable) {
+    if (!ErrorCheckAndCoerce(context, *var, value)) {
+        return nullptr;
+    }
     const Type* baseType = &var->type();
     int arraySize = 0;
     if (baseType->isArray()) {
         arraySize = baseType->columns();
         baseType = &baseType->componentType();
     }
-    return VarDeclaration::Make(context, var, baseType, arraySize, std::move(value));
+    std::unique_ptr<Statement> varDecl = VarDeclaration::Make(context, var.get(), baseType,
+            arraySize, std::move(value));
+    if (!varDecl) {
+        return nullptr;
+    }
+
+    // Detect the declaration of magical variables.
+    if ((var->storage() == Variable::Storage::kGlobal) && var->name() == Compiler::FRAGCOLOR_NAME) {
+        // Silently ignore duplicate definitions of `sk_FragColor`.
+        const Symbol* symbol = (*ThreadContext::SymbolTable())[var->name()];
+        if (symbol) {
+            return nullptr;
+        }
+    } else if ((var->storage() == Variable::Storage::kGlobal ||
+                var->storage() == Variable::Storage::kInterfaceBlock) &&
+               var->name() == Compiler::RTADJUST_NAME) {
+        // `sk_RTAdjust` is special, and makes the IR generator emit position-fixup expressions.
+        if (ThreadContext::IRGenerator().fRTAdjust) {
+            context.fErrors->error(var->fLine, "duplicate definition of 'sk_RTAdjust'");
+            return nullptr;
+        }
+        if (var->type() != *context.fTypes.fFloat4) {
+            context.fErrors->error(var->fLine, "sk_RTAdjust must have type 'float4'");
+            return nullptr;
+        }
+        ThreadContext::IRGenerator().fRTAdjust = var.get();
+    }
+
+    if (addToSymbolTable) {
+        ThreadContext::SymbolTable()->add(std::move(var));
+    } else {
+        ThreadContext::SymbolTable()->takeOwnershipOfSymbol(std::move(var));
+    }
+    return varDecl;
 }
 
-std::unique_ptr<Statement> VarDeclaration::Make(const Context& context,
-                                                Variable* var,
-                                                const Type* baseType,
-                                                int arraySize,
-                                                std::unique_ptr<Expression> value) {
+std::unique_ptr<Statement> VarDeclaration::Make(const Context& context, Variable* var,
+        const Type* baseType, int arraySize, std::unique_ptr<Expression> value) {
     SkASSERT(!baseType->isArray());
     // function parameters cannot have variable declarations
     SkASSERT(var->storage() != Variable::Storage::kParameter);
diff --git a/src/sksl/ir/SkSLVarDeclarations.h b/src/sksl/ir/SkSLVarDeclarations.h
index e93bf87..aeb445e 100644
--- a/src/sksl/ir/SkSLVarDeclarations.h
+++ b/src/sksl/ir/SkSLVarDeclarations.h
@@ -45,10 +45,15 @@
         }
     }
 
+    // Checks the modifiers, baseType, and storage for compatibility with one another and reports
+    // errors if needed. This method is implicitly called during Convert(), but is also explicitly
+    // called while processing interface block fields.
+    static void ErrorCheck(const Context& context, int line, const Modifiers& modifiers,
+            const Type* baseType, Variable::Storage storage);
+
     // Does proper error checking and type coercion; reports errors via ErrorReporter.
-    static std::unique_ptr<Statement> Convert(const Context& context,
-                                              Variable* var,
-                                              std::unique_ptr<Expression> value);
+    static std::unique_ptr<Statement> Convert(const Context& context, std::unique_ptr<Variable> var,
+            std::unique_ptr<Expression> value, bool addToSymbolTable = true);
 
     // Reports errors via ASSERT.
     static std::unique_ptr<Statement> Make(const Context& context,
@@ -87,6 +92,9 @@
     String description() const override;
 
 private:
+    static bool ErrorCheckAndCoerce(const Context& context, const Variable& var,
+            std::unique_ptr<Expression>& value);
+
     const Variable* fVar;
     const Type& fBaseType;
     int fArraySize;  // zero means "not an array"
diff --git a/src/sksl/ir/SkSLVariable.cpp b/src/sksl/ir/SkSLVariable.cpp
index 0179af9..55b4e96 100644
--- a/src/sksl/ir/SkSLVariable.cpp
+++ b/src/sksl/ir/SkSLVariable.cpp
@@ -7,8 +7,11 @@
 
 #include "src/sksl/ir/SkSLVariable.h"
 
+#include "src/sksl/SkSLCompiler.h"
 #include "src/sksl/SkSLContext.h"
 #include "src/sksl/SkSLMangler.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLThreadContext.h"
 #include "src/sksl/ir/SkSLSymbolTable.h"
 #include "src/sksl/ir/SkSLVarDeclarations.h"
 
@@ -25,6 +28,34 @@
     return fDeclaration ? fDeclaration->value().get() : nullptr;
 }
 
+std::unique_ptr<Variable> Variable::Convert(const Context& context, int line,
+        const Modifiers& modifiers, const Type* baseType, skstd::string_view name, bool isArray,
+        std::unique_ptr<Expression> arraySize, Variable::Storage storage) {
+    if (modifiers.fLayout.fLocation == 0 && modifiers.fLayout.fIndex == 0 &&
+        (modifiers.fFlags & Modifiers::kOut_Flag) &&
+        context.fConfig->fKind == ProgramKind::kFragment && name != Compiler::FRAGCOLOR_NAME) {
+        context.fErrors->error(line, "out location=0, index=0 is reserved for sk_FragColor");
+    }
+    return Make(context, line, modifiers, baseType, name, isArray, std::move(arraySize), storage);
+}
+
+std::unique_ptr<Variable> Variable::Make(const Context& context, int line,
+        const Modifiers& modifiers, const Type* baseType, skstd::string_view name, bool isArray,
+        std::unique_ptr<Expression> arraySize, Variable::Storage storage) {
+    const Type* type = baseType;
+    int arraySizeValue = 0;
+    if (isArray) {
+        SkASSERT(arraySize);
+        arraySizeValue = type->convertArraySize(context, std::move(arraySize));
+        if (!arraySizeValue) {
+            return nullptr;
+        }
+        type = ThreadContext::SymbolTable()->addArrayDimension(type, arraySizeValue);
+    }
+    return std::make_unique<Variable>(line, context.fModifiersPool->add(modifiers), name, type,
+            context.fConfig->fIsBuiltinCode, storage);
+}
+
 Variable::ScratchVariable Variable::MakeScratchVariable(const Context& context,
                                                         skstd::string_view baseName,
                                                         const Type* type,
diff --git a/src/sksl/ir/SkSLVariable.h b/src/sksl/ir/SkSLVariable.h
index 1a01b6e..c2b4957 100644
--- a/src/sksl/ir/SkSLVariable.h
+++ b/src/sksl/ir/SkSLVariable.h
@@ -51,6 +51,13 @@
 
     ~Variable() override;
 
+    static std::unique_ptr<Variable> Convert(const Context& context, int line,
+            const Modifiers& modifiers, const Type* baseType, skstd::string_view name, bool isArray,
+            std::unique_ptr<Expression> arraySize, Variable::Storage storage);
+
+    static std::unique_ptr<Variable> Make(const Context& context, int line,
+            const Modifiers& modifiers, const Type* baseType, skstd::string_view name, bool isArray,
+            std::unique_ptr<Expression> arraySize, Variable::Storage storage);
     /**
      * Creates a local scratch variable and the associated VarDeclaration statement.
      * Useful when doing IR rewrites, e.g. inlining a function call.