SkSL: Use type-specific sampling intrinsics, rather than fn-call syntax

After further discussion, using intrinsics with signatures similar to
sample keeps us looking like GLSL. However, using "sample" is still
misleading, so this adds explicit "shade", "filter", and "blend"
intrinsics. After migrating clients, the "sample" versions will be
removed.

Bug: skia:12302
Change-Id: Ia03e4b3794fc1fc5ae3c3099a7a350343ec7702e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/441457
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 09e518d..edb3552 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -19,10 +19,10 @@
 
   * There is a new syntax for invoking (sampling) child effects in SkSL. Previously, children
     (shaders, colorFilters, blenders) were invoked using different overloads of `sample`. That
-    syntax is deprecated (but still supported). Now, the child object can be invoked directly,
-    like a function. The arguments to these invocations are the same as the arguments that followed
-    the child in calls to `sample`.
-    https://review.skia.org/436517
+    syntax is deprecated (but still supported). Now, the name of the intrinsic used is dependent on
+    the type of the child: 'shade' for shaders, 'filter' for colorFilters, and 'blend' for blenders.
+    The arguments to these invocations are the same as the arguments in the old overloads of sample.
+    https://review.skia.org/441457
 
 * * *
 
diff --git a/resources/sksl/runtime_errors/IllegalShaderSampling.rts b/resources/sksl/runtime_errors/IllegalShaderSampling.rts
index e98c082..7e373b5 100644
--- a/resources/sksl/runtime_errors/IllegalShaderSampling.rts
+++ b/resources/sksl/runtime_errors/IllegalShaderSampling.rts
@@ -1,4 +1,4 @@
-// Expect 12 errors
+// Expect 36 errors
 
 uniform shader      s;
 uniform colorFilter f;
@@ -20,4 +20,45 @@
 half4 blender_empty()    { return sample(b); }
 half4 blender_color()    { return sample(b, color); }
 half4 blender_xy()       { return sample(b, xy); }
-half4 blender_xy_color() { return sample(b, xy, color); }
\ No newline at end of file
+half4 blender_xy_color() { return sample(b, xy, color); }
+
+// Same as above, but using the type-specific functions (shade, filter, blend):
+
+half4 shader_xy_color()  { return shade(s, xy, color); }
+half4 shader_color()     { return shade(s, color); }
+half4 shader_color_xy()  { return shade(s, color, xy); }
+half4 shader_empty()     { return shade(s); }
+half4 shader_matrix()    { return shade(s, float3x3(1)); }
+
+half4 filter_empty()     { return filter(f); }
+half4 filter_xy()        { return filter(f, xy); }
+half4 filter_xy_color()  { return filter(f, xy, color); }
+
+half4 blender_empty()    { return blend(b); }
+half4 blender_color()    { return blend(b, color); }
+half4 blender_xy()       { return blend(b, xy); }
+half4 blender_xy_color() { return blend(b, xy, color); }
+
+// Try to invoke a child with the wrong type-specific function, with either
+// argument list (aligned to function, or child type):
+
+half4 blend_shader_b()   { return blend(s, color, color); }
+half4 blend_shader_s()   { return blend(s, xy); }
+half4 filter_shader_f()  { return filter(s, color); }
+half4 filter_shader_s()  { return filter(s, xy); }
+
+half4 blend_filter_b()   { return blend(f, color, color); }
+half4 blend_filter_f()   { return blend(f, color); }
+half4 shade_filter_s()   { return shade(f, xy); }
+half4 shade_filter_f()   { return shade(f, color); }
+
+half4 filter_blender_f() { return filter(b, color); }
+half4 filter_blender_b() { return filter(b, color, color); }
+half4 shade_blender_s()  { return shade(b, xy); }
+half4 shade_blender_b()  { return shade(b, color, color); }
+
+// Correct usage (EXPECT NO ERRORS)
+
+half4 blend_blender() { return blend(b, color, color); }
+half4 filter_filter() { return filter(f, color); }
+half4 shade_shader()  { return shade(s, xy); }
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 79f9b26..e7a5dd9 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -25,7 +25,6 @@
 #include "src/sksl/ir/SkSLBinaryExpression.h"
 #include "src/sksl/ir/SkSLBoolLiteral.h"
 #include "src/sksl/ir/SkSLBreakStatement.h"
-#include "src/sksl/ir/SkSLChildCall.h"
 #include "src/sksl/ir/SkSLConstructor.h"
 #include "src/sksl/ir/SkSLContinueStatement.h"
 #include "src/sksl/ir/SkSLDiscardStatement.h"
@@ -1293,18 +1292,6 @@
 std::unique_ptr<Expression> IRGenerator::call(int offset,
                                               const FunctionDeclaration& function,
                                               ExpressionArray arguments) {
-    if (function.intrinsicKind() == k_sample_IntrinsicKind && arguments.size() >= 1 &&
-        arguments[0]->type().isEffectChild()) {
-        // Translate old-style sample(child, ...) calls into new-style child(...) IR
-        SkASSERT(arguments[0]->is<VariableReference>());
-        const Variable& child = *arguments[0]->as<VariableReference>().variable();
-        ExpressionArray argumentsWithoutChild;
-        for (size_t i = 1; i < arguments.size(); i++) {
-            argumentsWithoutChild.push_back(std::move(arguments[i]));
-        }
-        return ChildCall::Convert(fContext, offset, child, std::move(argumentsWithoutChild));
-    }
-
     if (function.isBuiltin()) {
         if (function.intrinsicKind() == k_dFdy_IntrinsicKind) {
             fInputs.fUseFlipRTUniform = true;
@@ -1404,16 +1391,6 @@
             }
             return this->call(offset, *functions[0], std::move(arguments));
         }
-        case Expression::Kind::kVariableReference: {
-            if (!functionValue->type().isEffectChild()) {
-                this->errorReporter().error(offset, "not a function");
-                return nullptr;
-            }
-            return ChildCall::Convert(fContext,
-                                      offset,
-                                      *functionValue->as<VariableReference>().variable(),
-                                      std::move(arguments));
-        }
         default:
             this->errorReporter().error(offset, "not a function");
             return nullptr;
diff --git a/src/sksl/SkSLIntrinsicList.h b/src/sksl/SkSLIntrinsicList.h
index 74c8f6d..207192a 100644
--- a/src/sksl/SkSLIntrinsicList.h
+++ b/src/sksl/SkSLIntrinsicList.h
@@ -21,6 +21,7 @@
     SKSL_INTRINSIC(atanh)            \
     SKSL_INTRINSIC(atan)             \
     SKSL_INTRINSIC(bitCount)         \
+    SKSL_INTRINSIC(blend)            \
     SKSL_INTRINSIC(ceil)             \
     SKSL_INTRINSIC(clamp)            \
     SKSL_INTRINSIC(cosh)             \
@@ -38,6 +39,7 @@
     SKSL_INTRINSIC(exp2)             \
     SKSL_INTRINSIC(exp)              \
     SKSL_INTRINSIC(faceforward)      \
+    SKSL_INTRINSIC(filter)           \
     SKSL_INTRINSIC(findLSB)          \
     SKSL_INTRINSIC(findMSB)          \
     SKSL_INTRINSIC(floatBitsToInt)   \
@@ -86,6 +88,7 @@
     SKSL_INTRINSIC(round)            \
     SKSL_INTRINSIC(sample)           \
     SKSL_INTRINSIC(saturate)         \
+    SKSL_INTRINSIC(shade)            \
     SKSL_INTRINSIC(sign)             \
     SKSL_INTRINSIC(sinh)             \
     SKSL_INTRINSIC(sin)              \
diff --git a/src/sksl/generated/sksl_rt_blend.dehydrated.sksl b/src/sksl/generated/sksl_rt_blend.dehydrated.sksl
index 177776c..73a3b73 100644
--- a/src/sksl/generated/sksl_rt_blend.dehydrated.sksl
+++ b/src/sksl/generated/sksl_rt_blend.dehydrated.sksl
@@ -1,18 +1,21 @@
-static uint8_t SKSL_INCLUDE_sksl_rt_blend[] = {74,0,
+static uint8_t SKSL_INCLUDE_sksl_rt_blend[] = {93,0,
 1,115,
 6,115,104,97,100,101,114,
 6,99,111,111,114,100,115,
 6,102,108,111,97,116,50,
-6,115,97,109,112,108,101,
+5,115,104,97,100,101,
 5,104,97,108,102,52,
 1,102,
 11,99,111,108,111,114,70,105,108,116,101,114,
 5,99,111,108,111,114,
+6,102,105,108,116,101,114,
 1,98,
 7,98,108,101,110,100,101,114,
 3,115,114,99,
 3,100,115,116,
-48,12,0,
+5,98,108,101,110,100,
+6,115,97,109,112,108,101,
+48,22,0,
 52,1,0,
 17,2,0,
 49,2,0,4,0,3,
@@ -21,36 +24,69 @@
 49,4,0,18,0,3,
 29,5,0,
 17,25,0,2,1,0,3,0,
-49,6,0,32,0,
+49,6,0,31,0,
 52,7,0,
-17,38,0,
-49,8,0,40,0,3,
+17,37,0,
+49,8,0,39,0,3,
 52,9,0,
-17,52,0,
+17,51,0,
 46,6,0,3,
-51,10,0,2,
-46,5,0,
-29,11,0,
-17,25,0,2,7,0,9,0,
+29,10,0,
+17,57,0,2,7,0,9,0,
 46,6,0,
-46,11,0,
-52,12,0,
-17,58,0,
-49,13,0,60,0,3,
+52,11,0,
+17,64,0,
+49,12,0,66,0,3,
+52,13,0,
+17,74,0,
+46,6,0,3,
 52,14,0,
-17,68,0,
+17,78,0,
 46,6,0,3,
-52,15,0,
-17,72,0,
-46,6,0,3,
-51,16,0,3,
-46,5,0,
-46,11,0,
-29,17,0,
-17,25,0,3,12,0,14,0,15,0,
+29,15,0,
+17,82,0,3,11,0,13,0,14,0,
 46,6,0,
-46,17,0,1,0,
-10,0,
+52,16,0,
+17,2,0,
+46,2,0,3,
+52,17,0,
+17,11,0,
+46,4,0,3,
+29,18,0,
+17,88,0,2,16,0,17,0,
+46,6,0,
+52,19,0,
+17,37,0,
+46,8,0,3,
+52,20,0,
+17,51,0,
+46,6,0,3,
+51,21,0,2,
+46,18,0,
+29,22,0,
+17,88,0,2,19,0,20,0,
+46,6,0,
+46,22,0,
+52,23,0,
+17,64,0,
+46,12,0,3,
+52,24,0,
+17,74,0,
+46,6,0,3,
+52,25,0,
+17,78,0,
+46,6,0,3,
+51,26,0,3,
+46,18,0,
+46,22,0,
+29,27,0,
+17,88,0,3,23,0,24,0,25,0,
+46,6,0,
+46,27,0,4,0,
+9,0,
+5,0,
+20,0,
+2,0,
 20,
 21,};
 static constexpr size_t SKSL_INCLUDE_sksl_rt_blend_LENGTH = sizeof(SKSL_INCLUDE_sksl_rt_blend);
diff --git a/src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl b/src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl
index 1c10c4f..27b0351 100644
--- a/src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl
+++ b/src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl
@@ -1,18 +1,21 @@
-static uint8_t SKSL_INCLUDE_sksl_rt_colorfilter[] = {74,0,
+static uint8_t SKSL_INCLUDE_sksl_rt_colorfilter[] = {93,0,
 1,115,
 6,115,104,97,100,101,114,
 6,99,111,111,114,100,115,
 6,102,108,111,97,116,50,
-6,115,97,109,112,108,101,
+5,115,104,97,100,101,
 5,104,97,108,102,52,
 1,102,
 11,99,111,108,111,114,70,105,108,116,101,114,
 5,99,111,108,111,114,
+6,102,105,108,116,101,114,
 1,98,
 7,98,108,101,110,100,101,114,
 3,115,114,99,
 3,100,115,116,
-48,12,0,
+5,98,108,101,110,100,
+6,115,97,109,112,108,101,
+48,22,0,
 52,1,0,
 17,2,0,
 49,2,0,4,0,3,
@@ -21,36 +24,69 @@
 49,4,0,18,0,3,
 29,5,0,
 17,25,0,2,1,0,3,0,
-49,6,0,32,0,
+49,6,0,31,0,
 52,7,0,
-17,38,0,
-49,8,0,40,0,3,
+17,37,0,
+49,8,0,39,0,3,
 52,9,0,
-17,52,0,
+17,51,0,
 46,6,0,3,
-51,10,0,2,
-46,5,0,
-29,11,0,
-17,25,0,2,7,0,9,0,
+29,10,0,
+17,57,0,2,7,0,9,0,
 46,6,0,
-46,11,0,
-52,12,0,
-17,58,0,
-49,13,0,60,0,3,
+52,11,0,
+17,64,0,
+49,12,0,66,0,3,
+52,13,0,
+17,74,0,
+46,6,0,3,
 52,14,0,
-17,68,0,
+17,78,0,
 46,6,0,3,
-52,15,0,
-17,72,0,
-46,6,0,3,
-51,16,0,3,
-46,5,0,
-46,11,0,
-29,17,0,
-17,25,0,3,12,0,14,0,15,0,
+29,15,0,
+17,82,0,3,11,0,13,0,14,0,
 46,6,0,
-46,17,0,1,0,
-10,0,
+52,16,0,
+17,2,0,
+46,2,0,3,
+52,17,0,
+17,11,0,
+46,4,0,3,
+29,18,0,
+17,88,0,2,16,0,17,0,
+46,6,0,
+52,19,0,
+17,37,0,
+46,8,0,3,
+52,20,0,
+17,51,0,
+46,6,0,3,
+51,21,0,2,
+46,18,0,
+29,22,0,
+17,88,0,2,19,0,20,0,
+46,6,0,
+46,22,0,
+52,23,0,
+17,64,0,
+46,12,0,3,
+52,24,0,
+17,74,0,
+46,6,0,3,
+52,25,0,
+17,78,0,
+46,6,0,3,
+51,26,0,3,
+46,18,0,
+46,22,0,
+29,27,0,
+17,88,0,3,23,0,24,0,25,0,
+46,6,0,
+46,27,0,4,0,
+9,0,
+5,0,
+20,0,
+2,0,
 20,
 21,};
 static constexpr size_t SKSL_INCLUDE_sksl_rt_colorfilter_LENGTH = sizeof(SKSL_INCLUDE_sksl_rt_colorfilter);
diff --git a/src/sksl/generated/sksl_rt_shader.dehydrated.sksl b/src/sksl/generated/sksl_rt_shader.dehydrated.sksl
index 3583af5..34821a8 100644
--- a/src/sksl/generated/sksl_rt_shader.dehydrated.sksl
+++ b/src/sksl/generated/sksl_rt_shader.dehydrated.sksl
@@ -1,20 +1,23 @@
-static uint8_t SKSL_INCLUDE_sksl_rt_shader[] = {94,0,
+static uint8_t SKSL_INCLUDE_sksl_rt_shader[] = {113,0,
 12,115,107,95,70,114,97,103,67,111,111,114,100,
 6,102,108,111,97,116,52,
 1,115,
 6,115,104,97,100,101,114,
 6,99,111,111,114,100,115,
 6,102,108,111,97,116,50,
-6,115,97,109,112,108,101,
+5,115,104,97,100,101,
 5,104,97,108,102,52,
 1,102,
 11,99,111,108,111,114,70,105,108,116,101,114,
 5,99,111,108,111,114,
+6,102,105,108,116,101,114,
 1,98,
 7,98,108,101,110,100,101,114,
 3,115,114,99,
 3,100,115,116,
-48,13,0,
+5,98,108,101,110,100,
+6,115,97,109,112,108,101,
+48,23,0,
 52,1,0,
 36,
 35,0,2,0,0,255,255,255,255,255,15,0,255,255,255,255,0,2,0,
@@ -27,36 +30,69 @@
 49,6,0,38,0,3,
 29,7,0,
 17,45,0,2,3,0,5,0,
-49,8,0,52,0,
+49,8,0,51,0,
 52,9,0,
-17,58,0,
-49,10,0,60,0,3,
+17,57,0,
+49,10,0,59,0,3,
 52,11,0,
-17,72,0,
+17,71,0,
 46,8,0,3,
-51,12,0,2,
-46,7,0,
-29,13,0,
-17,45,0,2,9,0,11,0,
+29,12,0,
+17,77,0,2,9,0,11,0,
 46,8,0,
-46,13,0,
-52,14,0,
-17,78,0,
-49,15,0,80,0,3,
+52,13,0,
+17,84,0,
+49,14,0,86,0,3,
+52,15,0,
+17,94,0,
+46,8,0,3,
 52,16,0,
-17,88,0,
+17,98,0,
 46,8,0,3,
-52,17,0,
-17,92,0,
-46,8,0,3,
-51,18,0,3,
-46,7,0,
-46,13,0,
-29,19,0,
-17,45,0,3,14,0,16,0,17,0,
+29,17,0,
+17,102,0,3,13,0,15,0,16,0,
 46,8,0,
-46,19,0,2,0,
-11,0,
+52,18,0,
+17,22,0,
+46,4,0,3,
+52,19,0,
+17,31,0,
+46,6,0,3,
+29,20,0,
+17,108,0,2,18,0,19,0,
+46,8,0,
+52,21,0,
+17,57,0,
+46,10,0,3,
+52,22,0,
+17,71,0,
+46,8,0,3,
+51,23,0,2,
+46,20,0,
+29,24,0,
+17,108,0,2,21,0,22,0,
+46,8,0,
+46,24,0,
+52,25,0,
+17,84,0,
+46,14,0,3,
+52,26,0,
+17,94,0,
+46,8,0,3,
+52,27,0,
+17,98,0,
+46,8,0,3,
+51,28,0,3,
+46,20,0,
+46,24,0,
+29,29,0,
+17,108,0,3,25,0,26,0,27,0,
+46,8,0,
+46,29,0,5,0,
+10,0,
+6,0,
+21,0,
+3,0,
 0,0,
 20,
 54,
diff --git a/src/sksl/ir/SkSLChildCall.cpp b/src/sksl/ir/SkSLChildCall.cpp
index b01107c..90ab36e 100644
--- a/src/sksl/ir/SkSLChildCall.cpp
+++ b/src/sksl/ir/SkSLChildCall.cpp
@@ -29,63 +29,47 @@
 }
 
 String ChildCall::description() const {
-    String result = String(this->child().name()) + "(";
-    String separator;
+    String result;
+    switch (this->child().type().typeKind()) {
+        case Type::TypeKind::kBlender:     result += "blend(";  break;
+        case Type::TypeKind::kColorFilter: result += "filter("; break;
+        case Type::TypeKind::kShader:      result += "shade(";  break;
+        default: SkUNREACHABLE;
+    }
+    result += this->child().name();
     for (const std::unique_ptr<Expression>& arg : this->arguments()) {
-        result += separator;
+        result += ", ";
         result += arg->description();
-        separator = ", ";
     }
     result += ")";
     return result;
 }
 
-struct ChildCallSignature {
-    const Type*               fReturnType = nullptr;
-    SkSTArray<2, const Type*> fParamTypes;
-};
-
-static ChildCallSignature child_call_signature(const Context& context, const Variable& child) {
-    const Type* half4  = context.fTypes.fHalf4.get();
+[[maybe_unused]] static bool call_signature_is_valid(const Context& context,
+                                                     const Variable& child,
+                                                     const ExpressionArray& arguments) {
+    const Type* half4 = context.fTypes.fHalf4.get();
     const Type* float2 = context.fTypes.fFloat2.get();
 
-    switch (child.type().typeKind()) {
-        case Type::TypeKind::kBlender:     return { half4, { half4, half4 } };
-        case Type::TypeKind::kColorFilter: return { half4, { half4 } };
-        case Type::TypeKind::kShader:      return { half4, { float2 } };
-        default:
-            SkUNREACHABLE;
-    }
-}
-
-std::unique_ptr<Expression> ChildCall::Convert(const Context& context,
-                                               int offset,
-                                               const Variable& child,
-                                               ExpressionArray arguments) {
-    ChildCallSignature signature = child_call_signature(context, child);
-    skstd::string_view typeName = child.type().name();
-
-    // Reject function calls with the wrong number of arguments.
-    if (signature.fParamTypes.size() != arguments.size()) {
-        String msg = "call to '" + typeName + "' expected " +
-                     to_string((int)signature.fParamTypes.size()) + " argument";
-        if (signature.fParamTypes.size() != 1) {
-            msg += "s";
+    auto params = [&]() -> SkSTArray<2, const Type*> {
+        switch (child.type().typeKind()) {
+            case Type::TypeKind::kBlender:     return { half4, half4 };
+            case Type::TypeKind::kColorFilter: return { half4 };
+            case Type::TypeKind::kShader:      return { float2 };
+            default:
+                SkUNREACHABLE;
         }
-        msg += ", but found " + to_string(arguments.count());
-        context.fErrors->error(offset, msg);
-        return nullptr;
-    }
+    }();
 
+    if (params.size() != arguments.size()) {
+        return false;
+    }
     for (size_t i = 0; i < arguments.size(); i++) {
-        // Coerce each argument to the proper type.
-        arguments[i] = signature.fParamTypes[i]->coerceExpression(std::move(arguments[i]), context);
-        if (!arguments[i]) {
-            return nullptr;
+        if (arguments[i]->type() != *params[i]) {
+            return false;
         }
     }
-
-    return Make(context, offset, signature.fReturnType, child, std::move(arguments));
+    return true;
 }
 
 std::unique_ptr<Expression> ChildCall::Make(const Context& context,
@@ -93,14 +77,7 @@
                                             const Type* returnType,
                                             const Variable& child,
                                             ExpressionArray arguments) {
-#ifdef SK_DEBUG
-    ChildCallSignature signature = child_call_signature(context, child);
-    SkASSERT(signature.fParamTypes.size() == arguments.size());
-    for (size_t i = 0; i < arguments.size(); i++) {
-        SkASSERT(arguments[i]->type() == *signature.fParamTypes[i]);
-    }
-#endif
-
+    SkASSERT(call_signature_is_valid(context, child, arguments));
     return std::make_unique<ChildCall>(offset, returnType, &child, std::move(arguments));
 }
 
diff --git a/src/sksl/ir/SkSLChildCall.h b/src/sksl/ir/SkSLChildCall.h
index cf3c477..10ab498 100644
--- a/src/sksl/ir/SkSLChildCall.h
+++ b/src/sksl/ir/SkSLChildCall.h
@@ -26,13 +26,6 @@
             , fChild(*child)
             , fArguments(std::move(arguments)) {}
 
-    // Performs type conversion on arguments, determines return type, and reports errors via the
-    // ErrorReporter.
-    static std::unique_ptr<Expression> Convert(const Context& context,
-                                               int offset,
-                                               const Variable& child,
-                                               ExpressionArray arguments);
-
     // Creates the child call; reports errors via ASSERT.
     static std::unique_ptr<Expression> Make(const Context& context,
                                             int offset,
diff --git a/src/sksl/ir/SkSLFunctionCall.cpp b/src/sksl/ir/SkSLFunctionCall.cpp
index 51d3836..2d92043 100644
--- a/src/sksl/ir/SkSLFunctionCall.cpp
+++ b/src/sksl/ir/SkSLFunctionCall.cpp
@@ -10,6 +10,7 @@
 #include "src/sksl/SkSLContext.h"
 #include "src/sksl/SkSLProgramSettings.h"
 #include "src/sksl/ir/SkSLBoolLiteral.h"
+#include "src/sksl/ir/SkSLChildCall.h"
 #include "src/sksl/ir/SkSLConstructorCompound.h"
 #include "src/sksl/ir/SkSLFloatLiteral.h"
 #include "src/sksl/ir/SkSLFunctionCall.h"
@@ -869,6 +870,26 @@
         }
     }
 
+    switch (function.intrinsicKind()) {
+        case k_blend_IntrinsicKind:
+        case k_sample_IntrinsicKind:
+        case k_shade_IntrinsicKind:
+        case k_filter_IntrinsicKind: {
+            if (arguments.size() >= 1 && arguments[0]->type().isEffectChild()) {
+                // Translate these intrinsic calls into a ChildCall, which simplifies handling in
+                // the generators and analysis code
+                SkASSERT(arguments[0]->is<VariableReference>());
+                const Variable& child = *arguments[0]->as<VariableReference>().variable();
+                std::rotate(arguments.begin(), arguments.begin() + 1, arguments.end());
+                arguments.pop_back();
+                return ChildCall::Make(context, offset, returnType, child, std::move(arguments));
+            }
+            break;
+        }
+        default:
+            break;
+    }
+
     return Make(context, offset, returnType, function, std::move(arguments));
 }
 
diff --git a/src/sksl/sksl_rt_blend.sksl b/src/sksl/sksl_rt_blend.sksl
index 02d931d..8ac2762 100644
--- a/src/sksl/sksl_rt_blend.sksl
+++ b/src/sksl/sksl_rt_blend.sksl
@@ -1,3 +1,7 @@
+half4 shade(shader s, float2 coords);
+half4 filter(colorFilter f, half4 color);
+half4 blend(blender b, half4 src, half4 dst);
+
 half4 sample(shader s, float2 coords);
 half4 sample(colorFilter f, half4 color);
 half4 sample(blender b, half4 src, half4 dst);
diff --git a/src/sksl/sksl_rt_colorfilter.sksl b/src/sksl/sksl_rt_colorfilter.sksl
index 02d931d..8ac2762 100644
--- a/src/sksl/sksl_rt_colorfilter.sksl
+++ b/src/sksl/sksl_rt_colorfilter.sksl
@@ -1,3 +1,7 @@
+half4 shade(shader s, float2 coords);
+half4 filter(colorFilter f, half4 color);
+half4 blend(blender b, half4 src, half4 dst);
+
 half4 sample(shader s, float2 coords);
 half4 sample(colorFilter f, half4 color);
 half4 sample(blender b, half4 src, half4 dst);
diff --git a/src/sksl/sksl_rt_shader.sksl b/src/sksl/sksl_rt_shader.sksl
index 5e24a04..7f199b9 100644
--- a/src/sksl/sksl_rt_shader.sksl
+++ b/src/sksl/sksl_rt_shader.sksl
@@ -1,5 +1,9 @@
 layout(builtin=15) float4 sk_FragCoord;
 
+half4 shade(shader s, float2 coords);
+half4 filter(colorFilter f, half4 color);
+half4 blend(blender b, half4 src, half4 dst);
+
 half4 sample(shader s, float2 coords);
 half4 sample(colorFilter f, half4 color);
 half4 sample(blender b, half4 src, half4 dst);
diff --git a/tests/SkSLDSLTest.cpp b/tests/SkSLDSLTest.cpp
index 84741cd..4a5359a 100644
--- a/tests/SkSLDSLTest.cpp
+++ b/tests/SkSLDSLTest.cpp
@@ -1947,8 +1947,8 @@
 DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLSampleShader, r, ctxInfo) {
     AutoDSLContext context(ctxInfo.directContext()->priv().getGpu(), default_settings(),
                            SkSL::ProgramKind::kRuntimeShader);
-    DSLGlobalVar shader(kUniform_Modifier, kShader_Type, "shader");
-    EXPECT_EQUAL(Sample(shader, Float2(0, 0)), "shader(float2(0.0, 0.0))");
+    DSLGlobalVar shader(kUniform_Modifier, kShader_Type, "child");
+    EXPECT_EQUAL(Sample(shader, Float2(0, 0)), "shade(child, float2(0.0, 0.0))");
 
     {
         ExpectError error(r, "no match for sample(shader, half4)");
diff --git a/tests/sksl/runtime_errors/IllegalShaderSampling.skvm b/tests/sksl/runtime_errors/IllegalShaderSampling.skvm
index bc6173a..a28654d 100644
--- a/tests/sksl/runtime_errors/IllegalShaderSampling.skvm
+++ b/tests/sksl/runtime_errors/IllegalShaderSampling.skvm
@@ -12,4 +12,28 @@
 error: 21: no match for sample(blender, half4)
 error: 22: no match for sample(blender, float2)
 error: 23: no match for sample(blender, float2, half4)
-12 errors
+error: 27: call to 'shade' expected 2 arguments, but found 3
+error: 28: expected 'float2', but found 'half4'
+error: 29: call to 'shade' expected 2 arguments, but found 3
+error: 30: call to 'shade' expected 2 arguments, but found 1
+error: 31: expected 'float2', but found 'float3x3'
+error: 33: call to 'filter' expected 2 arguments, but found 1
+error: 34: expected 'half4', but found 'float2'
+error: 35: call to 'filter' expected 2 arguments, but found 3
+error: 37: call to 'blend' expected 3 arguments, but found 1
+error: 38: call to 'blend' expected 3 arguments, but found 2
+error: 39: call to 'blend' expected 3 arguments, but found 2
+error: 40: expected 'half4', but found 'float2'
+error: 45: expected 'blender', but found 'shader'
+error: 46: call to 'blend' expected 3 arguments, but found 2
+error: 47: expected 'colorFilter', but found 'shader'
+error: 48: expected 'colorFilter', but found 'shader'
+error: 50: expected 'blender', but found 'colorFilter'
+error: 51: call to 'blend' expected 3 arguments, but found 2
+error: 52: expected 'shader', but found 'colorFilter'
+error: 53: expected 'shader', but found 'colorFilter'
+error: 55: expected 'colorFilter', but found 'blender'
+error: 56: call to 'filter' expected 2 arguments, but found 3
+error: 57: expected 'shader', but found 'blender'
+error: 58: call to 'shade' expected 2 arguments, but found 3
+36 errors