Add $es3 modifier to SkSL.

This modifier is currently allowed on built-in functions only.

The presence of this modifier will be used to indicate intrinsics which
are ES3-specific (and therefore, not allowed in user code under typical
circumstances).

Change-Id: Ice6be8d9d1b2bf0c8f07f2a89f335bb2f90f6681
Bug: skia:12202
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/439057
Commit-Queue: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/gn/sksl_tests.gni b/gn/sksl_tests.gni
index 45d4225..2649970 100644
--- a/gn/sksl_tests.gni
+++ b/gn/sksl_tests.gni
@@ -76,6 +76,7 @@
   "/sksl/errors/MatrixToVectorCastTooSmall.sksl",
   "/sksl/errors/MismatchedNumbers.sksl",
   "/sksl/errors/ModifiersInStruct.sksl",
+  "/sksl/errors/NoES3ModifierInUserCode.sksl",
   "/sksl/errors/OpaqueTypeAssignment.sksl",
   "/sksl/errors/OpaqueTypeConstruction.sksl",
   "/sksl/errors/OpaqueTypeInArray.sksl",
diff --git a/include/private/SkSLModifiers.h b/include/private/SkSLModifiers.h
index aca2484..8a129b2 100644
--- a/include/private/SkSLModifiers.h
+++ b/include/private/SkSLModifiers.h
@@ -32,6 +32,7 @@
         kLowp_Flag           = 1 <<  9,
         kInline_Flag         = 1 <<  10,
         kNoInline_Flag       = 1 <<  11,
+        kES3_Flag            = 1 <<  12,
     };
 
     Modifiers()
@@ -44,6 +45,9 @@
 
     String description() const {
         String result = fLayout.description();
+        if (fFlags & kES3_Flag) {
+            result += "$es3 ";
+        }
         if (fFlags & kUniform_Flag) {
             result += "uniform ";
         }
diff --git a/resources/sksl/errors/NoES3ModifierInUserCode.sksl b/resources/sksl/errors/NoES3ModifierInUserCode.sksl
new file mode 100644
index 0000000..f9ce51b
--- /dev/null
+++ b/resources/sksl/errors/NoES3ModifierInUserCode.sksl
@@ -0,0 +1,2 @@
+$es3 void fn() {}
+
diff --git a/src/sksl/SkSLDSLParser.cpp b/src/sksl/SkSLDSLParser.cpp
index bca062c..debccf6 100644
--- a/src/sksl/SkSLDSLParser.cpp
+++ b/src/sksl/SkSLDSLParser.cpp
@@ -36,6 +36,7 @@
         case Token::Kind::TK_HIGHP:          return Modifiers::kHighp_Flag;
         case Token::Kind::TK_MEDIUMP:        return Modifiers::kMediump_Flag;
         case Token::Kind::TK_LOWP:           return Modifiers::kLowp_Flag;
+        case Token::Kind::TK_ES3:            return Modifiers::kES3_Flag;
         default:                             return 0;
     }
 }
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index d0b1bc0..1e643bd 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -742,6 +742,7 @@
         { Modifiers::kHighp_Flag,          "highp" },
         { Modifiers::kMediump_Flag,        "mediump" },
         { Modifiers::kLowp_Flag,           "lowp" },
+        { Modifiers::kES3_Flag,            "$es3" },
     };
 
     int modifierFlags = modifiers.fFlags;
@@ -1335,6 +1336,9 @@
  */
 CoercionCost IRGenerator::callCost(const FunctionDeclaration& function,
                                    const ExpressionArray& arguments) {
+    if (this->strictES2Mode() && (function.modifiers().fFlags & Modifiers::kES3_Flag)) {
+        return CoercionCost::Impossible();
+    }
     if (function.parameters().size() != arguments.size()) {
         return CoercionCost::Impossible();
     }
diff --git a/src/sksl/SkSLParser.cpp b/src/sksl/SkSLParser.cpp
index df9b479..316c0fd 100644
--- a/src/sksl/SkSLParser.cpp
+++ b/src/sksl/SkSLParser.cpp
@@ -55,6 +55,7 @@
         case Token::Kind::TK_HIGHP:          return Modifiers::kHighp_Flag;
         case Token::Kind::TK_MEDIUMP:        return Modifiers::kMediump_Flag;
         case Token::Kind::TK_LOWP:           return Modifiers::kLowp_Flag;
+        case Token::Kind::TK_ES3:            return Modifiers::kES3_Flag;
         default:                             return 0;
     }
 }
diff --git a/src/sksl/ir/SkSLFunctionCall.cpp b/src/sksl/ir/SkSLFunctionCall.cpp
index 38f364f..02677d6 100644
--- a/src/sksl/ir/SkSLFunctionCall.cpp
+++ b/src/sksl/ir/SkSLFunctionCall.cpp
@@ -738,6 +738,12 @@
                                                   int offset,
                                                   const FunctionDeclaration& function,
                                                   ExpressionArray arguments) {
+    // Reject ES3 function calls in strict ES2 mode.
+    if (context.fConfig->strictES2Mode() && (function.modifiers().fFlags & Modifiers::kES3_Flag)) {
+        context.errors().error(offset, "call to '" + function.description() + "' is not supported");
+        return nullptr;
+    }
+
     // Reject function calls with the wrong number of arguments.
     if (function.parameters().size() != arguments.size()) {
         String msg = "call to '" + function.name() + "' expected " +
diff --git a/src/sksl/ir/SkSLFunctionDeclaration.cpp b/src/sksl/ir/SkSLFunctionDeclaration.cpp
index 1162b66..7892947 100644
--- a/src/sksl/ir/SkSLFunctionDeclaration.cpp
+++ b/src/sksl/ir/SkSLFunctionDeclaration.cpp
@@ -28,13 +28,15 @@
     return kNotIntrinsic;
 }
 
-static bool check_modifiers(const Context& context, int offset, const Modifiers& modifiers) {
-    IRGenerator::CheckModifiers(
-            context,
-            offset,
-            modifiers,
-            Modifiers::kHasSideEffects_Flag | Modifiers::kInline_Flag | Modifiers::kNoInline_Flag,
-            /*permittedLayoutFlags=*/0);
+static bool check_modifiers(const Context& context,
+                            int offset,
+                            const Modifiers& modifiers,
+                            bool isBuiltin) {
+    const int permitted = Modifiers::kHasSideEffects_Flag |
+                          Modifiers::kInline_Flag |
+                          Modifiers::kNoInline_Flag |
+                          (isBuiltin ? Modifiers::kES3_Flag : 0);
+    IRGenerator::CheckModifiers(context, offset, modifiers, permitted, /*permittedLayoutFlags=*/0);
     if ((modifiers.fFlags & Modifiers::kInline_Flag) &&
         (modifiers.fFlags & Modifiers::kNoInline_Flag)) {
         context.errors().error(offset, "functions cannot be both 'inline' and 'noinline'");
@@ -312,7 +314,7 @@
     bool isMain = (name == "main");
 
     const FunctionDeclaration* decl = nullptr;
-    if (!check_modifiers(context, offset, *modifiers) ||
+    if (!check_modifiers(context, offset, *modifiers, isBuiltin) ||
         !check_return_type(context, offset, *returnType, isBuiltin) ||
         !check_parameters(context, parameters, isMain, isBuiltin) ||
         (isMain && !check_main_signature(context, offset, *returnType, parameters, isBuiltin)) ||
diff --git a/tests/sksl/errors/NoES3ModifierInUserCode.glsl b/tests/sksl/errors/NoES3ModifierInUserCode.glsl
new file mode 100644
index 0000000..5192933
--- /dev/null
+++ b/tests/sksl/errors/NoES3ModifierInUserCode.glsl
@@ -0,0 +1,4 @@
+### Compilation failed:
+
+error: 1: '$es3' is not permitted here
+1 error