Added an API for creating RuntimeEffects using the SkSL DSL.

Change-Id: I305016d305455e2b90fe904d8da93cf7735cc38e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/389316
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
diff --git a/gn/sksl.gni b/gn/sksl.gni
index 5a3e5d8..ad9a00d 100644
--- a/gn/sksl.gni
+++ b/gn/sksl.gni
@@ -26,6 +26,7 @@
   "$_include/sksl/DSLExpression.h",
   "$_include/sksl/DSLFunction.h",
   "$_include/sksl/DSLModifiers.h",
+  "$_include/sksl/DSLRuntimeEffects.h",
   "$_include/sksl/DSLStatement.h",
   "$_include/sksl/DSLType.h",
   "$_include/sksl/DSLVar.h",
@@ -80,6 +81,7 @@
   "$_src/sksl/dsl/DSLCore.cpp",
   "$_src/sksl/dsl/DSLExpression.cpp",
   "$_src/sksl/dsl/DSLFunction.cpp",
+  "$_src/sksl/dsl/DSLRuntimeEffects.cpp",
   "$_src/sksl/dsl/DSLStatement.cpp",
   "$_src/sksl/dsl/DSLType.cpp",
   "$_src/sksl/dsl/DSLVar.cpp",
diff --git a/gn/tests.gni b/gn/tests.gni
index 5b56c58..e5bb1e6 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -245,6 +245,7 @@
   "$_tests/SkColor4fTest.cpp",
   "$_tests/SkColorSpaceXformStepsTest.cpp",
   "$_tests/SkDOMTest.cpp",
+  "$_tests/SkDSLRuntimeEffectTest.cpp",
   "$_tests/SkFixed15Test.cpp",
   "$_tests/SkGaussFilterTest.cpp",
   "$_tests/SkGlyphBufferTest.cpp",
diff --git a/include/effects/SkRuntimeEffect.h b/include/effects/SkRuntimeEffect.h
index f658bad..90b6303 100644
--- a/include/effects/SkRuntimeEffect.h
+++ b/include/effects/SkRuntimeEffect.h
@@ -126,6 +126,10 @@
         return Make(std::move(sksl), Options{});
     }
 
+    static Result MakeForColorFilter(std::unique_ptr<SkSL::Program> program);
+
+    static Result MakeForShader(std::unique_ptr<SkSL::Program> program);
+
     sk_sp<SkShader> makeShader(sk_sp<SkData> uniforms,
                                sk_sp<SkShader> children[],
                                size_t childCount,
@@ -203,8 +207,13 @@
                     std::vector<Varying>&& varyings,
                     uint32_t flags);
 
+    static Result Make(std::unique_ptr<SkSL::Program> program, SkSL::ProgramKind kind);
+
     static Result Make(SkString sksl, const Options& options, SkSL::ProgramKind kind);
 
+    static Result Make(SkString sksl, std::unique_ptr<SkSL::Program> program,
+                       const Options& options, SkSL::ProgramKind kind);
+
     uint32_t hash() const { return fHash; }
     bool usesSampleCoords() const { return (fFlags & kUsesSampleCoords_Flag); }
     bool allowShader()      const { return (fFlags & kAllowShader_Flag);      }
diff --git a/include/sksl/DSLModifiers.h b/include/sksl/DSLModifiers.h
index 3ba5bd4..83515b7 100644
--- a/include/sksl/DSLModifiers.h
+++ b/include/sksl/DSLModifiers.h
@@ -41,6 +41,7 @@
     SkSL::Modifiers fModifiers;
 
     friend DSLType Struct(const char* name, SkTArray<DSLField> fields);
+    friend class DSLFunction;
     friend class DSLVar;
     friend class DSLWriter;
 };
diff --git a/include/sksl/DSLRuntimeEffects.h b/include/sksl/DSLRuntimeEffects.h
new file mode 100644
index 0000000..194850f
--- /dev/null
+++ b/include/sksl/DSLRuntimeEffects.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_DSL_RUNTIME_EFFECTS
+#define SKSL_DSL_RUNTIME_EFFECTS
+
+#include "include/sksl/DSL.h"
+
+class SkRuntimeEffect;
+
+namespace SkSL {
+
+class Compiler;
+
+namespace dsl {
+
+#ifndef SKSL_STANDALONE
+
+void StartRuntimeShader(SkSL::Compiler* compiler);
+
+sk_sp<SkRuntimeEffect> EndRuntimeShader();
+
+#endif
+
+} // namespace dsl
+
+} // namespace SkSL
+
+#endif
diff --git a/include/sksl/DSLType.h b/include/sksl/DSLType.h
index 52ebe12..fba8ab9 100644
--- a/include/sksl/DSLType.h
+++ b/include/sksl/DSLType.h
@@ -58,6 +58,7 @@
     kInt2_Type,
     kInt3_Type,
     kInt4_Type,
+    kShader_Type,
     kShort_Type,
     kShort2_Type,
     kShort3_Type,
diff --git a/include/sksl/DSLVar.h b/include/sksl/DSLVar.h
index 4baf42a..3821b68 100644
--- a/include/sksl/DSLVar.h
+++ b/include/sksl/DSLVar.h
@@ -40,6 +40,10 @@
 
     ~DSLVar();
 
+    const char* name() const {
+        return fName;
+    }
+
     DSLExpression x() {
         return DSLExpression(*this).x();
     }
@@ -115,10 +119,6 @@
      */
     DSLVar(const char* name);
 
-    const char* name() const {
-        return fName;
-    }
-
     DSLModifiers fModifiers;
     // We only need to keep track of the type here so that we can create the SkSL::Variable. For
     // predefined variables this field is unnecessary, so we don't bother tracking it and just set
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index 50dc7ae..755b3d9 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -109,31 +109,67 @@
                               const SkSL::Type* type,
                               SkRuntimeEffect::Uniform* v) {
     using Type = SkRuntimeEffect::Uniform::Type;
+    if (*type == *ctx.fTypes.fFloat)    { v->type = Type::kFloat;    return true; }
+    if (*type == *ctx.fTypes.fHalf)     { v->type = Type::kFloat;    return true; }
+    if (*type == *ctx.fTypes.fFloat2)   { v->type = Type::kFloat2;   return true; }
+    if (*type == *ctx.fTypes.fHalf2)    { v->type = Type::kFloat2;   return true; }
+    if (*type == *ctx.fTypes.fFloat3)   { v->type = Type::kFloat3;   return true; }
+    if (*type == *ctx.fTypes.fHalf3)    { v->type = Type::kFloat3;   return true; }
+    if (*type == *ctx.fTypes.fFloat4)   { v->type = Type::kFloat4;   return true; }
+    if (*type == *ctx.fTypes.fHalf4)    { v->type = Type::kFloat4;   return true; }
+    if (*type == *ctx.fTypes.fFloat2x2) { v->type = Type::kFloat2x2; return true; }
+    if (*type == *ctx.fTypes.fHalf2x2)  { v->type = Type::kFloat2x2; return true; }
+    if (*type == *ctx.fTypes.fFloat3x3) { v->type = Type::kFloat3x3; return true; }
+    if (*type == *ctx.fTypes.fHalf3x3)  { v->type = Type::kFloat3x3; return true; }
+    if (*type == *ctx.fTypes.fFloat4x4) { v->type = Type::kFloat4x4; return true; }
+    if (*type == *ctx.fTypes.fHalf4x4)  { v->type = Type::kFloat4x4; return true; }
 
-    if (type == ctx.fTypes.fFloat.get())    { v->type = Type::kFloat;    return true; }
-    if (type == ctx.fTypes.fHalf.get())     { v->type = Type::kFloat;    return true; }
-    if (type == ctx.fTypes.fFloat2.get())   { v->type = Type::kFloat2;   return true; }
-    if (type == ctx.fTypes.fHalf2.get())    { v->type = Type::kFloat2;   return true; }
-    if (type == ctx.fTypes.fFloat3.get())   { v->type = Type::kFloat3;   return true; }
-    if (type == ctx.fTypes.fHalf3.get())    { v->type = Type::kFloat3;   return true; }
-    if (type == ctx.fTypes.fFloat4.get())   { v->type = Type::kFloat4;   return true; }
-    if (type == ctx.fTypes.fHalf4.get())    { v->type = Type::kFloat4;   return true; }
-    if (type == ctx.fTypes.fFloat2x2.get()) { v->type = Type::kFloat2x2; return true; }
-    if (type == ctx.fTypes.fHalf2x2.get())  { v->type = Type::kFloat2x2; return true; }
-    if (type == ctx.fTypes.fFloat3x3.get()) { v->type = Type::kFloat3x3; return true; }
-    if (type == ctx.fTypes.fHalf3x3.get())  { v->type = Type::kFloat3x3; return true; }
-    if (type == ctx.fTypes.fFloat4x4.get()) { v->type = Type::kFloat4x4; return true; }
-    if (type == ctx.fTypes.fHalf4x4.get())  { v->type = Type::kFloat4x4; return true; }
-
-    if (type == ctx.fTypes.fInt.get())  { v->type = Type::kInt;  return true; }
-    if (type == ctx.fTypes.fInt2.get()) { v->type = Type::kInt2; return true; }
-    if (type == ctx.fTypes.fInt3.get()) { v->type = Type::kInt3; return true; }
-    if (type == ctx.fTypes.fInt4.get()) { v->type = Type::kInt4; return true; }
+    if (*type == *ctx.fTypes.fInt)  { v->type = Type::kInt;  return true; }
+    if (*type == *ctx.fTypes.fInt2) { v->type = Type::kInt2; return true; }
+    if (*type == *ctx.fTypes.fInt3) { v->type = Type::kInt3; return true; }
+    if (*type == *ctx.fTypes.fInt4) { v->type = Type::kInt4; return true; }
 
     return false;
 }
 
+// TODO: Many errors aren't caught until we process the generated Program here. Catching those
+// in the IR generator would provide better errors messages (with locations).
+#define RETURN_FAILURE(...) return Result{nullptr, SkStringPrintf(__VA_ARGS__)}
+
+SkRuntimeEffect::Result SkRuntimeEffect::Make(SkString sksl, const Options& options,
+                                              SkSL::ProgramKind kind) {
+    std::unique_ptr<SkSL::Program> program;
+    {
+        // We keep this SharedCompiler in a separate scope to make sure it's destroyed before
+        // calling the Make overload at the end, which creates its own (non-reentrant)
+        // SharedCompiler instance
+        SkSL::SharedCompiler compiler;
+        SkSL::Program::Settings settings;
+        settings.fInlineThreshold = 0;
+        settings.fForceNoInline = options.forceNoInline;
+        settings.fAllowNarrowingConversions = true;
+        program = compiler->convertProgram(SkSL::ProgramKind::kRuntimeEffect,
+                                           SkSL::String(sksl.c_str(), sksl.size()),
+                                           settings);
+        // TODO: Many errors aren't caught until we process the generated Program here. Catching those
+        // in the IR generator would provide better errors messages (with locations).
+        #define RETURN_FAILURE(...) return Result{nullptr, SkStringPrintf(__VA_ARGS__)}
+
+        if (!program) {
+            RETURN_FAILURE("%s", compiler->errorText().c_str());
+        }
+    }
+    return Make(std::move(sksl), std::move(program), options, kind);
+}
+
+SkRuntimeEffect::Result SkRuntimeEffect::Make(std::unique_ptr<SkSL::Program> program,
+                                              SkSL::ProgramKind kind) {
+    SkString source(program->description().c_str());
+    return Make(std::move(source), std::move(program), Options{}, kind);
+}
+
 SkRuntimeEffect::Result SkRuntimeEffect::Make(SkString sksl,
+                                              std::unique_ptr<SkSL::Program> program,
                                               const Options& options,
                                               SkSL::ProgramKind kind) {
     SkSL::SharedCompiler compiler;
@@ -141,15 +177,6 @@
     settings.fInlineThreshold = 0;
     settings.fForceNoInline = options.forceNoInline;
     settings.fAllowNarrowingConversions = true;
-    auto program =
-            compiler->convertProgram(kind, SkSL::String(sksl.c_str(), sksl.size()), settings);
-    // TODO: Many errors aren't caught until we process the generated Program here. Catching those
-    // in the IR generator would provide better errors messages (with locations).
-    #define RETURN_FAILURE(...) return Result{nullptr, SkStringPrintf(__VA_ARGS__)}
-
-    if (!program) {
-        RETURN_FAILURE("%s", compiler->errorText().c_str());
-    }
 
     const SkSL::FunctionDefinition* main = nullptr;
     uint32_t flags = 0;
@@ -291,6 +318,18 @@
     return result;
 }
 
+SkRuntimeEffect::Result SkRuntimeEffect::MakeForColorFilter(std::unique_ptr<SkSL::Program> program) {
+    auto result = Make(std::move(program), SkSL::ProgramKind::kRuntimeColorFilter);
+    SkASSERT(!result.effect || result.effect->allowColorFilter());
+    return result;
+}
+
+SkRuntimeEffect::Result SkRuntimeEffect::MakeForShader(std::unique_ptr<SkSL::Program> program) {
+    auto result = Make(std::move(program), SkSL::ProgramKind::kRuntimeShader);
+    SkASSERT(!result.effect || result.effect->allowShader());
+    return result;
+}
+
 sk_sp<SkRuntimeEffect> SkMakeCachedRuntimeEffect(SkString sksl) {
     SK_BEGIN_REQUIRE_DENSE
     struct Key {
diff --git a/src/gpu/GrShaderUtils.h b/src/gpu/GrShaderUtils.h
index 83ef51e..f93dd12 100644
--- a/src/gpu/GrShaderUtils.h
+++ b/src/gpu/GrShaderUtils.h
@@ -10,8 +10,8 @@
 
 #include "include/core/SkTypes.h"
 #include "include/gpu/GrContextOptions.h"
+#include "include/private/SkSLProgramKind.h"
 #include "include/private/SkSLString.h"
-#include "src/sksl/ir/SkSLProgram.h"
 
 namespace GrShaderUtils {
 
diff --git a/src/sksl/SkSLContext.cpp b/src/sksl/SkSLContext.cpp
index d27a1ad..3ffc839 100644
--- a/src/sksl/SkSLContext.cpp
+++ b/src/sksl/SkSLContext.cpp
@@ -38,7 +38,8 @@
 Context::Context(ErrorReporter& errors, const ShaderCapsClass& caps)
         : fErrors(errors)
         , fCaps(caps)
-        , fDefined_Expression(std::make_unique<DefinedExpression>(fTypes.fInvalid.get())) {}
-
+        , fDefined_Expression(std::make_unique<DefinedExpression>(fTypes.fInvalid.get())) {
+    SkASSERT(!Pool::IsAttached());
+}
 }  // namespace SkSL
 
diff --git a/src/sksl/SkSLContext.h b/src/sksl/SkSLContext.h
index 55e5348..a1f1f49 100644
--- a/src/sksl/SkSLContext.h
+++ b/src/sksl/SkSLContext.h
@@ -12,6 +12,7 @@
 
 #include "src/sksl/SkSLBuiltinTypes.h"
 #include "src/sksl/SkSLErrorReporter.h"
+#include "src/sksl/SkSLPool.h"
 #include "src/sksl/SkSLUtil.h"
 #include "src/sksl/ir/SkSLExpression.h"
 #include "src/sksl/ir/SkSLType.h"
@@ -27,6 +28,10 @@
 public:
     Context(ErrorReporter& errors, const ShaderCapsClass& caps);
 
+    ~Context() {
+        SkASSERT(!Pool::IsAttached());
+    }
+
     // The Context holds all of the built-in types.
     BuiltinTypes fTypes;
 
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 645c594..d4f7845 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -1411,8 +1411,7 @@
                         std::make_unique<StructDefinition>(decl.fOffset, *type));
             }
         }
-        fProgramElements->push_back(std::make_unique<GlobalVarDeclaration>(decl.fOffset,
-                                                                           std::move(stmt)));
+        fProgramElements->push_back(std::make_unique<GlobalVarDeclaration>(std::move(stmt)));
     }
 }
 
@@ -2102,6 +2101,7 @@
     };
 
     BuiltinVariableScanner scanner(this);
+    SkASSERT(fProgramElements);
     for (auto& e : *fProgramElements) {
         scanner.visitProgramElement(*e);
     }
@@ -2131,6 +2131,8 @@
                         const std::vector<std::unique_ptr<ExternalFunction>>* externalFunctions,
                         std::vector<std::unique_ptr<ProgramElement>>* elements,
                         std::vector<const ProgramElement*>* sharedElements) {
+    fProgramElements = elements;
+    fSharedElements = sharedElements;
     fSymbolTable = base.fSymbols;
     fIntrinsics = base.fIntrinsics.get();
     if (fIntrinsics) {
@@ -2160,8 +2162,7 @@
         auto decl = VarDeclaration::Make(fContext, var.get(), fContext.fTypes.fInt.get(),
                                          /*arraySize=*/0, /*value=*/nullptr);
         fSymbolTable->add(std::move(var));
-        fProgramElements->push_back(
-                std::make_unique<GlobalVarDeclaration>(/*offset=*/-1, std::move(decl)));
+        fProgramElements->push_back(std::make_unique<GlobalVarDeclaration>(std::move(decl)));
     }
 
     if (externalFunctions) {
@@ -2220,9 +2221,6 @@
     std::vector<std::unique_ptr<ProgramElement>> elements;
     std::vector<const ProgramElement*> sharedElements;
 
-    fProgramElements = &elements;
-    fSharedElements = &sharedElements;
-
     this->start(base, isBuiltinCode, externalFunctions, &elements, &sharedElements);
 
     Parser parser(text, length, *fSymbolTable, this->errorReporter());
diff --git a/src/sksl/SkSLPool.cpp b/src/sksl/SkSLPool.cpp
index f2b2a8b..60b4f7e 100644
--- a/src/sksl/SkSLPool.cpp
+++ b/src/sksl/SkSLPool.cpp
@@ -70,6 +70,10 @@
     return pool;
 }
 
+bool Pool::IsAttached() {
+    return get_thread_local_memory_pool();
+}
+
 void Pool::attachToThread() {
     VLOG("ATTACH Pool:0x%016llX\n", (uint64_t)fMemPool.get());
     SkASSERT(get_thread_local_memory_pool() == nullptr);
diff --git a/src/sksl/SkSLPool.h b/src/sksl/SkSLPool.h
index 005310c..e464797 100644
--- a/src/sksl/SkSLPool.h
+++ b/src/sksl/SkSLPool.h
@@ -47,6 +47,8 @@
     // the pool can be destroyed.
     static void FreeMemory(void* ptr);
 
+    static bool IsAttached();
+
 private:
     void checkForLeaks();
 
diff --git a/src/sksl/SkSLRehydrator.cpp b/src/sksl/SkSLRehydrator.cpp
index 0021edc..cb6092c 100644
--- a/src/sksl/SkSLRehydrator.cpp
+++ b/src/sksl/SkSLRehydrator.cpp
@@ -332,7 +332,7 @@
         }
         case Rehydrator::kVarDeclarations_Command: {
             std::unique_ptr<Statement> decl = this->statement();
-            return std::make_unique<GlobalVarDeclaration>(/*offset=*/-1, std::move(decl));
+            return std::make_unique<GlobalVarDeclaration>(std::move(decl));
         }
         case Rehydrator::kStructDefinition_Command: {
             const Symbol* type = this->symbol();
diff --git a/src/sksl/codegen/SkSLVMCodeGenerator.cpp b/src/sksl/codegen/SkSLVMCodeGenerator.cpp
index 1989c9c..a122aca 100644
--- a/src/sksl/codegen/SkSLVMCodeGenerator.cpp
+++ b/src/sksl/codegen/SkSLVMCodeGenerator.cpp
@@ -1278,6 +1278,8 @@
         // This merges currentFunction().fReturned into fConditionMask. Lanes that conditionally
         // returned in the current function would otherwise resume execution within the child.
         ScopedCondition m(this, ~currentFunction().fReturned);
+        SkASSERTF(f.function().definition(), "no definition for function '%s'",
+                  f.function().description().c_str());
         this->writeFunction(*f.function().definition(), argVals, result.asSpan());
     }
 
diff --git a/src/sksl/dsl/DSLFunction.cpp b/src/sksl/dsl/DSLFunction.cpp
index 7e4f887..3aada20 100644
--- a/src/sksl/dsl/DSLFunction.cpp
+++ b/src/sksl/dsl/DSLFunction.cpp
@@ -22,6 +22,11 @@
                        std::vector<DSLVar*> params) {
     std::vector<const Variable*> paramVars;
     paramVars.reserve(params.size());
+    bool isMain = !strcmp(name, "main");
+    auto typeIsValidForColor = [&](const SkSL::Type& type) {
+        return type == *DSLWriter::Context().fTypes.fHalf4 ||
+               type == *DSLWriter::Context().fTypes.fFloat4;
+    };
     for (DSLVar* param : params) {
         // This counts as declaring the variable; make sure it hasn't been previously declared and
         // then kill its pending declaration statement. Otherwise the statement will hang around
@@ -37,6 +42,22 @@
                                    "initial values\n");
         }
         param->fDeclared = true;
+        param->fStorage = SkSL::VariableStorage::kParameter;
+        if (paramVars.empty()) {
+            SkSL::ProgramKind kind = DSLWriter::Context().fConfig->fKind;
+            if (isMain && (kind == ProgramKind::kRuntimeEffect ||
+                           kind == ProgramKind::kFragmentProcessor)) {
+                const SkSL::Type& type = param->fType.skslType();
+                // We verify that the signature is fully correct later. For now, if this is an .fp
+                // or runtime effect of any flavor, a float2 param is supposed to be the coords, and
+                // a half4/float parameter is supposed to be the input color:
+                if (type == *DSLWriter::Context().fTypes.fFloat2) {
+                    param->fModifiers.fModifiers.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN;
+                } else if (typeIsValidForColor(type)) {
+                    param->fModifiers.fModifiers.fLayout.fBuiltin = SK_INPUT_COLOR_BUILTIN;
+                }
+            }
+        }
         paramVars.push_back(&DSLWriter::Var(*param));
         param->fDeclaration = nullptr;
     }
@@ -44,7 +65,7 @@
     fDecl = symbols.add(std::make_unique<SkSL::FunctionDeclaration>(
                                              /*offset=*/-1,
                                              DSLWriter::Modifiers(SkSL::Modifiers()),
-                                             DSLWriter::Name(name),
+                                             isMain ? name : DSLWriter::Name(name),
                                              std::move(paramVars), fReturnType,
                                              /*builtin=*/false));
 }
@@ -61,6 +82,7 @@
         DSLWriter::Compiler().setErrorCount(0);
         SkASSERT(!DSLWriter::Compiler().errorCount());
     }
+    fDecl->fDefinition = function.get();
     DSLWriter::ProgramElements().push_back(std::move(function));
 }
 
diff --git a/src/sksl/dsl/DSLRuntimeEffects.cpp b/src/sksl/dsl/DSLRuntimeEffects.cpp
new file mode 100644
index 0000000..2408c03
--- /dev/null
+++ b/src/sksl/dsl/DSLRuntimeEffects.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/DSLRuntimeEffects.h"
+
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/sksl/DSLCore.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLIRGenerator.h"
+#include "src/sksl/dsl/priv/DSLWriter.h"
+
+namespace SkSL {
+
+namespace dsl {
+
+#ifndef SKSL_STANDALONE
+
+void StartRuntimeShader(SkSL::Compiler* compiler) {
+    Start(compiler, SkSL::ProgramKind::kRuntimeEffect);
+    SkSL::ProgramSettings& settings = DSLWriter::IRGenerator().fContext.fConfig->fSettings;
+    SkASSERT(settings.fInlineThreshold == SkSL::kDefaultInlineThreshold);
+    settings.fInlineThreshold = 0;
+    SkASSERT(!settings.fAllowNarrowingConversions);
+    settings.fAllowNarrowingConversions = true;
+}
+
+sk_sp<SkRuntimeEffect> EndRuntimeShader() {
+    std::unique_ptr<SkSL::Program> program = DSLWriter::ReleaseProgram();
+    auto result = SkRuntimeEffect::MakeForShader(std::move(program));
+    // TODO(skbug.com/11862): propagate errors properly
+    SkASSERTF(result.effect, "%s\n", result.errorText.c_str());
+    SkSL::ProgramSettings& settings = DSLWriter::IRGenerator().fContext.fConfig->fSettings;
+    settings.fInlineThreshold = SkSL::kDefaultInlineThreshold;
+    settings.fAllowNarrowingConversions = false;
+    End();
+    return result.effect;
+}
+
+#endif // SKSL_STANDALONE
+
+} // namespace dsl
+
+} // namespace SkSL
diff --git a/src/sksl/dsl/DSLType.cpp b/src/sksl/dsl/DSLType.cpp
index c1db0f5..9c8fa81 100644
--- a/src/sksl/dsl/DSLType.cpp
+++ b/src/sksl/dsl/DSLType.cpp
@@ -91,6 +91,8 @@
             return *context.fTypes.fInt3;
         case kInt4_Type:
             return *context.fTypes.fInt4;
+        case kShader_Type:
+            return *context.fTypes.fShader;
         case kShort_Type:
             return *context.fTypes.fShort;
         case kShort2_Type:
diff --git a/src/sksl/dsl/DSLVar.cpp b/src/sksl/dsl/DSLVar.cpp
index 437f0b9..9756672 100644
--- a/src/sksl/dsl/DSLVar.cpp
+++ b/src/sksl/dsl/DSLVar.cpp
@@ -9,6 +9,7 @@
 
 #include "include/sksl/DSLModifiers.h"
 #include "include/sksl/DSLType.h"
+#include "src/sksl/SkSLCompiler.h"
 #include "src/sksl/SkSLUtil.h"
 #include "src/sksl/dsl/priv/DSLWriter.h"
 #include "src/sksl/ir/SkSLBinaryExpression.h"
@@ -34,13 +35,23 @@
         // correctly-named variable with the right type, so we just create a placeholder for it.
         // TODO(skia/11330): we'll need to fix this when switching over to nodes.
         fVar = DSLWriter::SymbolTable()->takeOwnershipOfIRNode(
-                       std::make_unique<SkSL::Variable>(
-                                  /*offset=*/-1,
-                                  DSLWriter::IRGenerator().fModifiers->addToPool(SkSL::Modifiers()),
-                                  fName,
-                                  DSLWriter::Context().fTypes.fFloat2.get(),
-                                  /*builtin=*/true,
-                                  SkSL::VariableStorage::kGlobal));
+                std::make_unique<SkSL::Variable>(
+                        /*offset=*/-1,
+                        DSLWriter::IRGenerator().fModifiers->addToPool(
+                                SkSL::Modifiers(
+                                        SkSL::Layout(/*flags=*/0, /*location=*/-1, /*offset=*/-1,
+                                                     /*binding=*/-1, /*index=*/-1, /*set=*/-1,
+                                                     SK_MAIN_COORDS_BUILTIN,
+                                                     /*inputAttachmentIndex=*/-1,
+                                                     Layout::kUnspecified_Primitive,
+                                                     /*maxVertices=*/1, /*invocations=*/-1,
+                                                     /*marker=*/"", /*when=*/"",
+                                                     Layout::CType::kDefault),
+                                        SkSL::Modifiers::kNo_Flag)),
+                        fName,
+                        DSLWriter::Context().fTypes.fFloat2.get(),
+                        /*builtin=*/true,
+                        SkSL::VariableStorage::kGlobal));
         return;
     }
 #endif
@@ -62,7 +73,7 @@
     : fModifiers(std::move(modifiers))
     , fType(std::move(type))
     , fRawName(name)
-    , fName(DSLWriter::Name(name))
+    , fName(fType.skslType().isOpaque() ? name : DSLWriter::Name(name))
     , fInitialValue(std::move(initialValue))
     , fStorage(Variable::Storage::kLocal)
     , fDeclared(DSLWriter::Instance().fMarkVarsDeclared) {
diff --git a/src/sksl/dsl/priv/DSLWriter.cpp b/src/sksl/dsl/priv/DSLWriter.cpp
index f972c2b..1ed8c61 100644
--- a/src/sksl/dsl/priv/DSLWriter.cpp
+++ b/src/sksl/dsl/priv/DSLWriter.cpp
@@ -34,25 +34,28 @@
 DSLWriter::DSLWriter(SkSL::Compiler* compiler, SkSL::ProgramKind kind)
     : fCompiler(compiler) {
     SkSL::ParsedModule module = fCompiler->moduleForProgramKind(kind);
-    fConfig.fKind = kind;
+    fConfig = std::make_unique<ProgramConfig>();
+    fConfig->fKind = kind;
 
     SkSL::IRGenerator& ir = *fCompiler->fIRGenerator;
-    fOldSymbolTable = ir.fSymbolTable;
     fOldConfig = fCompiler->fContext->fConfig;
-    ir.fSymbolTable = module.fSymbols;
-    fCompiler->fContext->fConfig = &fConfig;
-    ir.pushSymbolTable();
+    fCompiler->fContext->fConfig = fConfig.get();
     if (compiler->context().fCaps.useNodePools()) {
         fPool = Pool::Create();
         fPool->attachToThread();
     }
+    ir.start(module, false, nullptr, &fProgramElements, &fSharedElements);
 }
 
 DSLWriter::~DSLWriter() {
-    SkSL::IRGenerator& ir = *fCompiler->fIRGenerator;
-    ir.fSymbolTable = fOldSymbolTable;
+    if (SymbolTable()) {
+        fCompiler->fIRGenerator->finish();
+        fProgramElements.clear();
+    } else {
+        // We should only be here with a null symbol table if ReleaseProgram was called
+        SkASSERT(fProgramElements.empty());
+    }
     fCompiler->fContext->fConfig = fOldConfig;
-    fProgramElements.clear();
     if (fPool) {
         fPool->detachFromThread();
     }
@@ -198,8 +201,10 @@
 
 const SkSL::Variable& DSLWriter::Var(DSLVar& var) {
     if (!var.fVar) {
-        DSLWriter::IRGenerator().checkVarDeclaration(/*offset=*/-1, var.fModifiers.fModifiers,
-                                                     &var.fType.skslType(), var.fStorage);
+        if (var.fStorage != SkSL::VariableStorage::kParameter) {
+            DSLWriter::IRGenerator().checkVarDeclaration(/*offset=*/-1, var.fModifiers.fModifiers,
+                                                         &var.fType.skslType(), var.fStorage);
+        }
         std::unique_ptr<SkSL::Variable> skslvar = DSLWriter::IRGenerator().convertVar(
                                                                           /*offset=*/-1,
                                                                           var.fModifiers.fModifiers,
@@ -214,6 +219,10 @@
         var.fDeclaration = DSLWriter::IRGenerator().convertVarDeclaration(
                                                                        std::move(skslvar),
                                                                        var.fInitialValue.release());
+        if (var.fStorage == Variable::Storage::kGlobal) {
+            DSLWriter::ProgramElements().push_back(std::make_unique<SkSL::GlobalVarDeclaration>(
+                                                                      std::move(var.fDeclaration)));
+        }
     }
     return *var.fVar;
 }
@@ -228,6 +237,27 @@
     var.fDeclared = true;
 }
 
+std::unique_ptr<SkSL::Program> DSLWriter::ReleaseProgram() {
+    SkSL::IRGenerator& ir = IRGenerator();
+    IRGenerator::IRBundle bundle = ir.finish();
+    Pool* pool = Instance().fPool.get();
+    auto result = std::make_unique<SkSL::Program>(/*source=*/nullptr,
+                                                  std::move(DSLWriter::Instance().fConfig),
+                                                  Compiler().fContext,
+                                                  std::move(bundle.fElements),
+                                                  std::move(bundle.fSharedElements),
+                                                  std::move(bundle.fModifiers),
+                                                  std::move(bundle.fSymbolTable),
+                                                  std::move(Instance().fPool),
+                                                  bundle.fInputs);
+    if (pool) {
+        pool->detachFromThread();
+    }
+    SkASSERT(ProgramElements().empty());
+    SkASSERT(!SymbolTable());
+    return result;
+}
+
 #if !SK_SUPPORT_GPU || defined(SKSL_STANDALONE)
 
 DSLWriter& DSLWriter::Instance() {
diff --git a/src/sksl/dsl/priv/DSLWriter.h b/src/sksl/dsl/priv/DSLWriter.h
index 8e47cbd..c2d936a 100644
--- a/src/sksl/dsl/priv/DSLWriter.h
+++ b/src/sksl/dsl/priv/DSLWriter.h
@@ -199,17 +199,19 @@
         return Instance().fMangle;
     }
 
+    static std::unique_ptr<SkSL::Program> ReleaseProgram();
+
     static DSLWriter& Instance();
 
     static void SetInstance(std::unique_ptr<DSLWriter> instance);
 
 private:
-    SkSL::ProgramConfig fConfig;
+    std::unique_ptr<SkSL::ProgramConfig> fConfig;
     SkSL::Compiler* fCompiler;
     std::unique_ptr<Pool> fPool;
-    std::shared_ptr<SkSL::SymbolTable> fOldSymbolTable;
     SkSL::ProgramConfig* fOldConfig;
     std::vector<std::unique_ptr<SkSL::ProgramElement>> fProgramElements;
+    std::vector<const SkSL::ProgramElement*> fSharedElements;
     ErrorHandler* fErrorHandler = nullptr;
     bool fMangle = true;
     bool fMarkVarsDeclared = false;
diff --git a/src/sksl/ir/SkSLFunctionDeclaration.h b/src/sksl/ir/SkSLFunctionDeclaration.h
index 5324cd5..8dda492 100644
--- a/src/sksl/ir/SkSLFunctionDeclaration.h
+++ b/src/sksl/ir/SkSLFunctionDeclaration.h
@@ -182,6 +182,8 @@
     bool fBuiltin;
     bool fIsMain;
 
+    friend class SkSL::dsl::DSLFunction;
+
     using INHERITED = Symbol;
 };
 
diff --git a/src/sksl/ir/SkSLProgram.h b/src/sksl/ir/SkSLProgram.h
index c762f4d..e44497e 100644
--- a/src/sksl/ir/SkSLProgram.h
+++ b/src/sksl/ir/SkSLProgram.h
@@ -191,6 +191,14 @@
     std::vector<std::unique_ptr<ProgramElement>>& ownedElements() { return fElements; }
     const std::vector<std::unique_ptr<ProgramElement>>& ownedElements() const { return fElements; }
 
+    String description() const {
+        String result;
+        for (const auto& e : this->elements()) {
+            result += e->description();
+        }
+        return result;
+    }
+
     std::unique_ptr<String> fSource;
     std::unique_ptr<ProgramConfig> fConfig;
     std::shared_ptr<Context> fContext;
diff --git a/src/sksl/ir/SkSLVarDeclarations.h b/src/sksl/ir/SkSLVarDeclarations.h
index aba1869..8a2b7ca 100644
--- a/src/sksl/ir/SkSLVarDeclarations.h
+++ b/src/sksl/ir/SkSLVarDeclarations.h
@@ -105,8 +105,8 @@
 public:
     static constexpr Kind kProgramElementKind = Kind::kGlobalVar;
 
-    GlobalVarDeclaration(int offset, std::unique_ptr<Statement> decl)
-            : INHERITED(offset, kProgramElementKind)
+    GlobalVarDeclaration(std::unique_ptr<Statement> decl)
+            : INHERITED(decl->fOffset, kProgramElementKind)
             , fDeclaration(std::move(decl)) {
         SkASSERT(this->declaration()->is<VarDeclaration>());
     }
@@ -120,7 +120,7 @@
     }
 
     std::unique_ptr<ProgramElement> clone() const override {
-        return std::make_unique<GlobalVarDeclaration>(fOffset, this->declaration()->clone());
+        return std::make_unique<GlobalVarDeclaration>(this->declaration()->clone());
     }
 
     String description() const override {
diff --git a/src/sksl/ir/SkSLVariable.h b/src/sksl/ir/SkSLVariable.h
index e38ca9b..92796fb 100644
--- a/src/sksl/ir/SkSLVariable.h
+++ b/src/sksl/ir/SkSLVariable.h
@@ -22,6 +22,7 @@
 
 namespace dsl {
 class DSLCore;
+class DSLFunction;
 } // namespace dsl
 
 enum class VariableStorage : int8_t {
@@ -89,6 +90,7 @@
     using INHERITED = Symbol;
 
     friend class dsl::DSLCore;
+    friend class dsl::DSLFunction;
     friend class VariableReference;
 };
 
diff --git a/tests/SkDSLRuntimeEffectTest.cpp b/tests/SkDSLRuntimeEffectTest.cpp
new file mode 100644
index 0000000..7568b80
--- /dev/null
+++ b/tests/SkDSLRuntimeEffectTest.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkBitmap.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkColorFilter.h"
+#include "include/core/SkData.h"
+#include "include/core/SkPaint.h"
+#include "include/core/SkSurface.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/gpu/GrDirectContext.h"
+#include "include/sksl/DSLRuntimeEffects.h"
+#include "src/core/SkTLazy.h"
+#include "src/gpu/GrColor.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "tests/Test.h"
+
+#include <algorithm>
+#include <thread>
+
+using namespace SkSL::dsl;
+
+class DSLTestEffect {
+public:
+    DSLTestEffect(skiatest::Reporter* r, sk_sp<SkSurface> surface)
+        : fReporter(r)
+        , fCaps(SkSL::ShaderCapsFactory::Standalone())
+        , fCompiler(std::make_unique<SkSL::Compiler>(fCaps.get()))
+        , fSurface(std::move(surface)) {}
+
+    void start() {
+        StartRuntimeShader(fCompiler.get());
+    }
+
+    void end() {
+        sk_sp<SkRuntimeEffect> effect = EndRuntimeShader();
+        SkASSERT(effect);
+        fBuilder.init(std::move(effect));
+    }
+
+    SkRuntimeShaderBuilder::BuilderUniform uniform(const char* name) {
+        return fBuilder->uniform(name);
+    }
+    SkRuntimeShaderBuilder::BuilderChild child(const char* name) {
+        return fBuilder->child(name);
+    }
+
+    using PreTestFn = std::function<void(SkCanvas*, SkPaint*)>;
+
+    void test(GrColor TL, GrColor TR, GrColor BL, GrColor BR,
+              PreTestFn preTestCallback = nullptr) {
+        auto shader = fBuilder->makeShader(nullptr, false);
+        if (!shader) {
+            REPORT_FAILURE(fReporter, "shader", SkString("Effect didn't produce a shader"));
+            return;
+        }
+
+        SkCanvas* canvas = fSurface->getCanvas();
+        SkPaint paint;
+        paint.setShader(std::move(shader));
+        paint.setBlendMode(SkBlendMode::kSrc);
+
+        canvas->save();
+        if (preTestCallback) {
+            preTestCallback(canvas, &paint);
+        }
+        canvas->drawPaint(paint);
+        canvas->restore();
+
+        GrColor actual[4];
+        SkImageInfo info = fSurface->imageInfo();
+        if (!fSurface->readPixels(info, actual, info.minRowBytes(), 0, 0)) {
+            REPORT_FAILURE(fReporter, "readPixels", SkString("readPixels failed"));
+            return;
+        }
+
+        GrColor expected[4] = {TL, TR, BL, BR};
+        if (0 != memcmp(actual, expected, sizeof(actual))) {
+            REPORT_FAILURE(fReporter, "Runtime effect didn't match expectations",
+                           SkStringPrintf("\n"
+                                          "Expected: [ %08x %08x %08x %08x ]\n"
+                                          "Got     : [ %08x %08x %08x %08x ]\n"
+                                          "SkSL:\n%s\n",
+                                          TL, TR, BL, BR, actual[0], actual[1], actual[2],
+                                          actual[3], fBuilder->effect()->source().c_str()));
+        }
+    }
+
+    void test(GrColor expected, PreTestFn preTestCallback = nullptr) {
+        this->test(expected, expected, expected, expected, preTestCallback);
+    }
+
+private:
+    skiatest::Reporter*             fReporter;
+    SkSL::ShaderCapsPointer         fCaps;
+    std::unique_ptr<SkSL::Compiler> fCompiler;
+    sk_sp<SkSurface>                fSurface;
+    SkTLazy<SkRuntimeShaderBuilder> fBuilder;
+};
+
+static void test_RuntimeEffect_Shaders(skiatest::Reporter* r, GrRecordingContext* rContext) {
+    SkImageInfo info = SkImageInfo::Make(2, 2, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+    sk_sp<SkSurface> surface = rContext
+                                    ? SkSurface::MakeRenderTarget(rContext, SkBudgeted::kNo, info)
+                                    : SkSurface::MakeRaster(info);
+    REPORTER_ASSERT(r, surface);
+    using float4 = std::array<float, 4>;
+    using int4 = std::array<int, 4>;
+    DSLTestEffect effect(r, surface);
+
+    // Local coords
+    {
+        effect.start();
+        Var p(kFloat2_Type, "p");
+        Function(kHalf4_Type, "main", p).define(
+            Return(Half4(Half2(p - 0.5), 0, 1))
+        );
+        effect.end();
+        effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+    }
+
+    // Use of a simple uniform. (Draw twice with two values to ensure it's updated).
+    {
+        effect.start();
+        Var gColor(kUniform_Modifier, kFloat4_Type);
+        Function(kHalf4_Type, "main").define(
+            Return(Half4(gColor))
+        );
+        effect.end();
+        effect.uniform(gColor.name()) = float4{ 0.0f, 0.25f, 0.75f, 1.0f };
+        effect.test(0xFFBF4000);
+        effect.uniform(gColor.name()) = float4{ 1.0f, 0.0f, 0.0f, 0.498f };
+        effect.test(0x7F00007F);  // Tests that we clamp to valid premul
+    }
+
+    // Same, with integer uniforms
+    {
+        effect.start();
+        Var gColor(kUniform_Modifier, kInt4_Type);
+        Function(kHalf4_Type, "main").define(
+            Return(Half4(gColor) / 255)
+        );
+        effect.end();
+        effect.uniform(gColor.name()) = int4{ 0x00, 0x40, 0xBF, 0xFF };
+        effect.test(0xFFBF4000);
+        effect.uniform(gColor.name()) = int4{ 0xFF, 0x00, 0x00, 0x7F };
+        effect.test(0x7F00007F);  // Tests that we clamp to valid premul
+    }
+
+    // Test sk_FragCoord (device coords). Rotate the canvas to be sure we're seeing device coords.
+    // Since the surface is 2x2, we should see (0,0), (1,0), (0,1), (1,1). Multiply by 0.498 to
+    // make sure we're not saturating unexpectedly.
+    {
+        effect.start();
+        Function(kHalf4_Type, "main").define(
+            Return(Half4(0.498 * (Half2(Swizzle(sk_FragCoord(), X, Y)) - 0.5), 0, 1))
+        );
+        effect.end();
+        effect.test(0xFF000000, 0xFF00007F, 0xFF007F00, 0xFF007F7F,
+                    [](SkCanvas* canvas, SkPaint*) { canvas->rotate(45.0f); });
+    }
+
+    // Runtime effects should use relaxed precision rules by default
+    {
+        effect.start();
+        Var p(kFloat2_Type, "p");
+        Function(kHalf4_Type, "main", p).define(
+            Return(Float4(p - 0.5, 0, 1))
+        );
+        effect.end();
+        effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+    }
+
+    // ... and support *returning* float4, not just half4
+    {
+        effect.start();
+        Var p(kFloat2_Type, "p");
+        Function(kFloat4_Type, "main", p).define(
+            Return(Float4(p - 0.5, 0, 1))
+        );
+        effect.end();
+        effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+    }
+
+    // Mutating coords should work. (skbug.com/10918)
+    {
+        effect.start();
+        Var p(kFloat2_Type, "p");
+        Function(kFloat4_Type, "main", p).define(
+            p -= 0.5,
+            Return(Float4(p, 0, 1))
+        );
+        effect.end();
+        effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+    }
+    {
+        effect.start();
+        Var p1(kInOut_Modifier, kFloat2_Type, "p");
+        Function moveCoords(kVoid_Type, "moveCoords", p1);
+        moveCoords.define(
+            p1 -= 0.5
+        );
+        Var p2(kFloat2_Type, "p");
+        Function(kFloat4_Type, "main", p2).define(
+            moveCoords(p2),
+            Return(Float4(p2, 0, 1))
+        );
+        effect.end();
+        effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+    }
+
+    //
+    // Sampling children
+    //
+
+    // Sampling a null child should return the paint color
+    {
+        effect.start();
+        Var child(kUniform_Modifier, kShader_Type, "child");
+        Function(kFloat4_Type, "main").define(
+            Return(Sample(child))
+        );
+        effect.end();
+        effect.child("child") = nullptr;
+        effect.test(0xFF00FFFF,
+                    [](SkCanvas*, SkPaint* paint) { paint->setColor4f({1.0f, 1.0f, 0.0f, 1.0f}); });
+    }
+}
+
+DEF_TEST(DSLRuntimeEffectSimple, r) {
+    test_RuntimeEffect_Shaders(r, nullptr);
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DSLRuntimeEffectSimple_GPU, r, ctxInfo) {
+    test_RuntimeEffect_Shaders(r, ctxInfo.directContext());
+}
diff --git a/tests/SkSLDSLTest.cpp b/tests/SkSLDSLTest.cpp
index 75f194f..34f4260 100644
--- a/tests/SkSLDSLTest.cpp
+++ b/tests/SkSLDSLTest.cpp
@@ -1492,7 +1492,7 @@
     // Uniforms do not need to be explicitly declared
 }
 
-DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLSample, r, ctxInfo) {
+DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLSampleFragmentProcessor, r, ctxInfo) {
     AutoDSLContext context(ctxInfo.directContext()->priv().getGpu(), /*markVarsDeclared=*/true,
                            SkSL::ProgramKind::kFragmentProcessor);
     DSLVar child(kUniform_Modifier, kFragmentProcessor_Type, "child");
@@ -1510,6 +1510,20 @@
     }
 }
 
+DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLSampleShader, r, ctxInfo) {
+    AutoDSLContext context(ctxInfo.directContext()->priv().getGpu(), /*markVarsDeclared=*/true,
+                           SkSL::ProgramKind::kRuntimeEffect);
+    DSLVar shader(kUniform_Modifier, kShader_Type, "shader");
+    EXPECT_EQUAL(Sample(shader), "sample(shader)");
+    EXPECT_EQUAL(Sample(shader, Float2(0, 0)), "sample(shader, float2(0.0, 0.0))");
+    EXPECT_EQUAL(Sample(shader, Float3x3(1)), "sample(shader, float3x3(1.0))");
+
+    {
+        ExpectError error(r, "error: no match for sample(shader, half4)\n");
+        Sample(shader, Half4(1)).release();
+    }
+}
+
 DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLStruct, r, ctxInfo) {
     AutoDSLContext context(ctxInfo.directContext()->priv().getGpu(), /*markVarsDeclared=*/false);